diff --git a/.eslintrc b/.eslintrc index 1eec39827..3fe5e4c3e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -38,6 +38,7 @@ "message": "Use `globalThis` instead" } ], + "prefer-rest-params": 0, "require-yield": 0, "eqeqeq": ["error", "smart"], "spaced-comment": [ diff --git a/package-lock.json b/package-lock.json index 0bdf4c708..7fa75c357 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,22 +10,23 @@ "license": "GPL-3.0", "dependencies": { "@matrixai/async-cancellable": "^1.1.1", - "@matrixai/async-init": "^1.8.4", + "@matrixai/async-init": "^1.10.0", "@matrixai/async-locks": "^4.0.0", "@matrixai/contexts": "^1.1.0", - "@matrixai/db": "^5.2.0", - "@matrixai/errors": "^1.1.7", + "@matrixai/db": "^5.3.0", + "@matrixai/errors": "^1.2.0", + "@matrixai/events": "^3.2.0", "@matrixai/id": "^3.3.6", "@matrixai/logger": "^3.1.0", - "@matrixai/quic": "^0.0.18", + "@matrixai/quic": "^0.1.3", "@matrixai/resources": "^1.1.5", "@matrixai/timer": "^1.1.1", "@matrixai/workers": "^1.3.7", "@peculiar/asn1-pkcs8": "^2.3.0", "@peculiar/asn1-schema": "^2.3.0", "@peculiar/asn1-x509": "^2.3.0", - "@peculiar/webcrypto": "1.4.0", - "@peculiar/x509": "1.8.3", + "@peculiar/webcrypto": "^1.4.3", + "@peculiar/x509": "^1.8.3", "@scure/bip39": "^1.1.0", "@streamparser/json": "^0.0.13", "@types/ws": "^8.5.4", @@ -52,37 +53,37 @@ }, "devDependencies": { "@fast-check/jest": "^1.1.0", - "@swc/core": "^1.3.62", - "@swc/jest": "^0.2.26", + "@swc/core": "1.3.82", + "@swc/jest": "^0.2.29", "@types/cross-spawn": "^6.0.2", - "@types/jest": "^28.1.3", - "@types/node": "^18.15.0", + "@types/jest": "^29.5.2", + "@types/node": "^20.5.7", "@types/pako": "^1.0.2", "@types/prompts": "^2.0.13", "@types/readable-stream": "^2.3.11", - "@typescript-eslint/eslint-plugin": "^5.45.1", - "@typescript-eslint/parser": "^5.45.1", + "@typescript-eslint/eslint-plugin": "^5.61.0", + "@typescript-eslint/parser": "^5.61.0", "benny": "^3.7.1", "common-tags": "^1.8.2", - "eslint": "^8.15.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-prettier": "^4.0.0", + "eslint": "^8.44.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-prettier": "^5.0.0-alpha.2", "fast-check": "^3.0.1", - "jest": "^28.1.1", - "jest-extended": "^3.0.1", - "jest-junit": "^14.0.0", + "jest": "^29.6.2", + "jest-extended": "^4.0.0", + "jest-junit": "^16.0.0", "jest-mock-props": "^1.9.1", "node-gyp-build": "^4.4.0", - "prettier": "^2.6.2", + "prettier": "^3.0.0", "shelljs": "^0.8.5", "shx": "^0.3.4", "systeminformation": "^5.18.5", - "ts-jest": "^28.0.5", + "ts-jest": "^29.1.1", "ts-node": "^10.9.1", "tsconfig-paths": "^3.9.0", - "typedoc": "^0.23.21", - "typescript": "^4.9.3" + "typedoc": "^0.24.8", + "typescript": "^5.1.6" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -553,7 +554,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -1004,97 +1004,59 @@ } }, "node_modules/@jest/console": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.4.tgz", + "integrity": "sha512-wNK6gC0Ha9QeEPSkeJedQuTQqxZYnDPuDcDhVuVatRvMkL4D0VTvFVZj+Yuh6caG2aOfzkUZ36KtCmLNtR02hw==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", + "jest-message-util": "^29.6.3", + "jest-util": "^29.6.3", "slash": "^3.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/console/node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/console/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/core": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", - "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.4.tgz", + "integrity": "sha512-U/vq5ccNTSVgYH7mHnodHmCffGWHJnz/E1BEWlLuK5pM4FZmGfBn/nrJGLjUsSmyx3otCeqc1T31F4y08AMDLg==", "dev": true, "dependencies": { - "@jest/console": "^28.1.3", - "@jest/reporters": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/console": "^29.6.4", + "@jest/reporters": "^29.6.4", + "@jest/test-result": "^29.6.4", + "@jest/transform": "^29.6.4", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^28.1.3", - "jest-config": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-resolve-dependencies": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "jest-watcher": "^28.1.3", + "jest-changed-files": "^29.6.3", + "jest-config": "^29.6.4", + "jest-haste-map": "^29.6.4", + "jest-message-util": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.6.4", + "jest-resolve-dependencies": "^29.6.4", + "jest-runner": "^29.6.4", + "jest-runtime": "^29.6.4", + "jest-snapshot": "^29.6.4", + "jest-util": "^29.6.3", + "jest-validate": "^29.6.3", + "jest-watcher": "^29.6.4", "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "rimraf": "^3.0.0", + "pretty-format": "^29.6.3", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -1105,153 +1067,6 @@ } } }, - "node_modules/@jest/core/node_modules/@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "dependencies": { - "jest-get-type": "^28.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/core/node_modules/diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/core/node_modules/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/core/node_modules/jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/core/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/core/node_modules/jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/core/node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/core/node_modules/jest-snapshot": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", - "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-haste-map": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "natural-compare": "^1.4.0", - "pretty-format": "^28.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/core/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, "node_modules/@jest/create-cache-key-function": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-27.5.1.tgz", @@ -1290,148 +1105,89 @@ } }, "node_modules/@jest/environment": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.2.tgz", - "integrity": "sha512-AEcW43C7huGd/vogTddNNTDRpO6vQ2zaQNrttvWV18ArBx9Z56h7BIsXkNFJVOO4/kblWEQz30ckw0+L3izc+Q==", - "dev": true, - "peer": true, - "dependencies": { - "@jest/fake-timers": "^29.6.2", - "@jest/types": "^29.6.1", - "@types/node": "*", - "jest-mock": "^29.6.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "29.6.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", - "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.4.tgz", + "integrity": "sha512-sQ0SULEjA1XUTHmkBRl7A1dyITM9yb1yb3ZNKPX3KlTd6IG7mWUe3e2yfExtC2Zz1Q+mMckOLHmL/qLiuQJrBQ==", "dev": true, - "peer": true, "dependencies": { - "@jest/schemas": "^29.6.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/fake-timers": "^29.6.4", + "@jest/types": "^29.6.3", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "jest-mock": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.2.tgz", - "integrity": "sha512-m6DrEJxVKjkELTVAztTLyS/7C92Y2b0VYqmDROYKLLALHn8T/04yPs70NADUYPrV3ruI+H3J0iUIuhkjp7vkfg==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.4.tgz", + "integrity": "sha512-Warhsa7d23+3X5bLbrbYvaehcgX5TLYhI03JKoedTiI8uJU4IhqYBWF7OSSgUyz4IgLpUYPkK0AehA5/fRclAA==", "dev": true, - "peer": true, "dependencies": { - "expect": "^29.6.2", - "jest-snapshot": "^29.6.2" + "expect": "^29.6.4", + "jest-snapshot": "^29.6.4" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.2.tgz", - "integrity": "sha512-6zIhM8go3RV2IG4aIZaZbxwpOzz3ZiM23oxAlkquOIole+G6TrbeXnykxWYlqF7kz2HlBjdKtca20x9atkEQYg==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.4.tgz", + "integrity": "sha512-FEhkJhqtvBwgSpiTrocquJCdXPsyvNKcl/n7A3u7X4pVoF4bswm11c9d4AV+kfq2Gpv/mM8x7E7DsRvH+djkrg==", "dev": true, - "peer": true, "dependencies": { - "jest-get-type": "^29.4.3" + "jest-get-type": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.2.tgz", - "integrity": "sha512-euZDmIlWjm1Z0lJ1D0f7a0/y5Kh/koLFMUBE5SUYWrmy8oNhJpbTBDAP6CxKnadcMLDoDf4waRYCe35cH6G6PA==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.4.tgz", + "integrity": "sha512-6UkCwzoBK60edXIIWb0/KWkuj7R7Qq91vVInOe3De6DSpaEiqjKcJw4F7XUet24Wupahj9J6PlR09JqJ5ySDHw==", "dev": true, - "peer": true, "dependencies": { - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.6.2", - "jest-mock": "^29.6.2", - "jest-util": "^29.6.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { - "version": "29.6.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", - "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", - "dev": true, - "peer": true, - "dependencies": { - "@jest/schemas": "^29.6.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "jest-message-util": "^29.6.3", + "jest-mock": "^29.6.3", + "jest-util": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.2.tgz", - "integrity": "sha512-cjuJmNDjs6aMijCmSa1g2TNG4Lby/AeU7/02VtpW+SLcZXzOLK2GpN2nLqcFjmhy3B3AoPeQVx7BnyOf681bAw==", - "dev": true, - "peer": true, - "dependencies": { - "@jest/environment": "^29.6.2", - "@jest/expect": "^29.6.2", - "@jest/types": "^29.6.1", - "jest-mock": "^29.6.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@jest/types": { - "version": "29.6.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", - "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.4.tgz", + "integrity": "sha512-wVIn5bdtjlChhXAzVXavcY/3PEjf4VqM174BM3eGL5kMxLiZD5CLnbmkEyA1Dwh9q8XjP6E8RwjBsY/iCWrWsA==", "dev": true, - "peer": true, "dependencies": { - "@jest/schemas": "^29.6.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@jest/environment": "^29.6.4", + "@jest/expect": "^29.6.4", + "@jest/types": "^29.6.3", + "jest-mock": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", - "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.4.tgz", + "integrity": "sha512-sxUjWxm7QdchdrD3NfWKrL8FBsortZeibSJv4XLjESOOjSUOkjQcb0ZHJwfhEGIvBvTluTzfG2yZWZhkrXJu8g==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", + "@jest/console": "^29.6.4", + "@jest/test-result": "^29.6.4", + "@jest/transform": "^29.6.4", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", @@ -1439,21 +1195,20 @@ "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", + "jest-message-util": "^29.6.3", + "jest-util": "^29.6.3", + "jest-worker": "^29.6.4", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", - "terminal-link": "^2.0.0", "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -1464,47 +1219,26 @@ } } }, - "node_modules/@jest/reporters/node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz", + "integrity": "sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": ">=10" } }, "node_modules/@jest/schemas": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", - "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.27.8" @@ -1514,99 +1248,88 @@ } }, "node_modules/@jest/source-map": { - "version": "28.1.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", - "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.13", + "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.4.tgz", + "integrity": "sha512-uQ1C0AUEN90/dsyEirgMLlouROgSY+Wc/JanVVk0OiUKa5UFh7sJpMEM3aoUBAz2BRNvUJ8j3d294WFuRxSyOQ==", "dev": true, "dependencies": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/console": "^29.6.4", + "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/test-sequencer": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", - "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.4.tgz", + "integrity": "sha512-E84M6LbpcRq3fT4ckfKs9ryVanwkaIB0Ws9bw3/yP4seRLg/VaCZ/LgW0MCq5wwk4/iP/qnilD41aj2fsw2RMg==", "dev": true, "dependencies": { - "@jest/test-result": "^28.1.3", + "@jest/test-result": "^29.6.4", "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", + "jest-haste-map": "^29.6.4", "slash": "^3.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/transform": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", - "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.4.tgz", + "integrity": "sha512-8thgRSiXUqtr/pPGY/OsyHuMjGyhVnWrFAwoxmIemlBuiMyU1WFs0tXoNxzcr4A4uErs/ABre76SGmrr5ab/AA==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", + "jest-haste-map": "^29.6.4", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.6.3", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" + "write-file-atomic": "^4.0.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } + "node_modules/@jest/transform/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true }, "node_modules/@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "dependencies": { - "@jest/schemas": "^28.1.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -1614,27 +1337,9 @@ "chalk": "^4.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/types/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/types/node_modules/@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", - "dev": true - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -1695,12 +1400,13 @@ "integrity": "sha512-f0yxu7dHwvffZ++7aCm2WIcCJn18uLcOTdCCwEA3R3KVHYE3TG/JNoTWD9/mqBkAV1AI5vBfJzg27WnF9rOUXQ==" }, "node_modules/@matrixai/async-init": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/@matrixai/async-init/-/async-init-1.8.4.tgz", - "integrity": "sha512-33cGC7kHTs9KKwMHJA5d5XURWhx3QUq7lLxPEXLoVfWdTHixcWNvtfshAOso0hbRfx1P3ZSgsb+ZHaIASHhWfg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@matrixai/async-init/-/async-init-1.10.0.tgz", + "integrity": "sha512-JjUFu6rqd+dtTHFJ6z8bjbceuFGBj/APWfJByVsfbEH1DJsOgWERFcW3DBUrS0mgTph4Vl518tsNcsSwKT5Y+g==", "dependencies": { "@matrixai/async-locks": "^4.0.0", - "@matrixai/errors": "^1.1.7" + "@matrixai/errors": "^1.2.0", + "@matrixai/events": "^3.2.0" } }, "node_modules/@matrixai/async-locks": { @@ -1715,9 +1421,9 @@ } }, "node_modules/@matrixai/contexts": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@matrixai/contexts/-/contexts-1.1.0.tgz", - "integrity": "sha512-sB4UrT8T6OICBujNxTOss8O+dAHnbfndBqZG0fO1PSZUgaZlXDg3cSz9ButbV4JLEz25UvPgh4ChvwTP31DUcQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@matrixai/contexts/-/contexts-1.2.0.tgz", + "integrity": "sha512-MR/B02Kf4UoliP9b/gMMKsvWV6QM4JSPKTIqrhQP2tbOl3FwLI+AIhL3vgYEj1Xw+PP8bY5cr8ontJ8x6AJyMg==", "dependencies": { "@matrixai/async-cancellable": "^1.1.1", "@matrixai/async-locks": "^4.0.0", @@ -1727,23 +1433,23 @@ } }, "node_modules/@matrixai/db": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-5.2.1.tgz", - "integrity": "sha512-pzbzzRSC0r7zgNkNlMEIirIxFsVTUaGrNVhSd/RczoY18WqwaMzCmO/pLAuMX9ML9MD5wAlRUctFz6qIibKybg==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-5.3.0.tgz", + "integrity": "sha512-MI43pA/XjkNceSUwTvj1ohOzcR/7pCRuhvmhT7u39NnUGQN1kpdxPfHKxDlX2B/zatExRkVNA+sPIhE3+sThYg==", "hasInstallScript": true, "dependencies": { - "@matrixai/async-init": "^1.8.4", + "@matrixai/async-init": "^1.9.1", "@matrixai/async-locks": "^4.0.0", - "@matrixai/errors": "^1.1.7", + "@matrixai/errors": "^1.2.0", "@matrixai/logger": "^3.1.0", "@matrixai/resources": "^1.1.5", - "@matrixai/workers": "^1.3.7", + "@matrixai/workers": "^1.4.0", "node-gyp-build": "4.4.0", "threads": "^1.6.5" }, "engines": { "msvs": "2019", - "node": "^18.15.0" + "node": "^20.5.1" } }, "node_modules/@matrixai/db/node_modules/node-gyp-build": { @@ -1757,13 +1463,21 @@ } }, "node_modules/@matrixai/errors": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@matrixai/errors/-/errors-1.1.7.tgz", - "integrity": "sha512-WD6MrlfgtNSTfXt60lbMgwasS5T7bdRgH4eYSOxV+KWngqlkEij9EoDt5LwdvcMD1yuC33DxPTnH4Xu2XV3nMw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@matrixai/errors/-/errors-1.2.0.tgz", + "integrity": "sha512-eZHPHFla5GFmi0O0yGgbtkca+ZjwpDbMz+60NC3y+DzQq6BMoe4gHmPjDalAHTxyxv0+Q+AWJTuV8Ows+IqBfQ==", "dependencies": { "ts-custom-error": "3.2.2" } }, + "node_modules/@matrixai/events": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@matrixai/events/-/events-3.2.0.tgz", + "integrity": "sha512-MiUr8cUQyGAZCU7EbbMZI1xiYtLnWi/FMSCYpuV+14cMtmU4qfZpCT/nUh+xUNZS3P/LOgR/VjW56BsrJTfICw==", + "engines": { + "node": ">=19.0.0" + } + }, "node_modules/@matrixai/id": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/@matrixai/id/-/id-3.3.6.tgz", @@ -1779,31 +1493,32 @@ "integrity": "sha512-C4JWpgbNik3V99bfGfDell5cH3JULD67eEq9CeXl4rYgsvanF8hhuY84ZYvndPhimt9qjA9/Z8uExKGoiv1zVw==" }, "node_modules/@matrixai/quic": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/@matrixai/quic/-/quic-0.0.18.tgz", - "integrity": "sha512-RuEvcjT5gsMNTtAZyjJNsQ2E9DJXz3+eODSfWYfUcnsBA35RvH+zhtOD+zsDYzW++kvN+SsXu+0ryCuLh7eV/Q==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@matrixai/quic/-/quic-0.1.3.tgz", + "integrity": "sha512-qjB2U/fHoPolDz+BVh4LUcVnPPBizbEI9I3s8/phNzwxx9tVNsBXRMzeJyNDCGyEjvNc8foMbIv80nmc6ykQBQ==", "dependencies": { "@matrixai/async-cancellable": "^1.1.1", - "@matrixai/async-init": "^1.8.4", + "@matrixai/async-init": "^1.10.0", "@matrixai/async-locks": "^4.0.0", - "@matrixai/contexts": "^1.1.0", - "@matrixai/errors": "^1.1.7", + "@matrixai/contexts": "^1.2.0", + "@matrixai/errors": "^1.2.0", + "@matrixai/events": "^3.2.0", "@matrixai/logger": "^3.1.0", "@matrixai/resources": "^1.1.5", "@matrixai/timer": "^1.1.1", "ip-num": "^1.5.0" }, "optionalDependencies": { - "@matrixai/quic-darwin-arm64": "0.0.18", - "@matrixai/quic-darwin-x64": "0.0.18", - "@matrixai/quic-linux-x64": "0.0.18", - "@matrixai/quic-win32-x64": "0.0.18" + "@matrixai/quic-darwin-arm64": "0.1.3", + "@matrixai/quic-darwin-x64": "0.1.3", + "@matrixai/quic-linux-x64": "0.1.3", + "@matrixai/quic-win32-x64": "0.1.3" } }, "node_modules/@matrixai/quic-darwin-arm64": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/@matrixai/quic-darwin-arm64/-/quic-darwin-arm64-0.0.18.tgz", - "integrity": "sha512-/xGDEBx4NZiKShyfJNLfcuCHqFCfY0g580ctQoZuczBKeehnACkF+NwXJ7Eu0pUMfht/G822wplUPN42xp/ixg==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@matrixai/quic-darwin-arm64/-/quic-darwin-arm64-0.1.3.tgz", + "integrity": "sha512-uMmMOhX5lxd51idxt32bsp3EWsPQ2iFBViXc310N33uD8tpFYOj4lRdR/0ck0+AdPuQwY0sFeT8lcC9/o8WjhA==", "cpu": [ "arm64" ], @@ -1813,9 +1528,9 @@ ] }, "node_modules/@matrixai/quic-darwin-x64": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/@matrixai/quic-darwin-x64/-/quic-darwin-x64-0.0.18.tgz", - "integrity": "sha512-bsGtWFXiv/LqGezLlcDSn3QnIWZxpZYSpI0mGkBqtaOuPHldIxuWETNMRyymWnuXvs1AletveODtNCoYcNqsNA==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@matrixai/quic-darwin-x64/-/quic-darwin-x64-0.1.3.tgz", + "integrity": "sha512-8D7SOHqMEziTN9t3Oc2TMo2P+NU/+rcfEonQb5syvD3TJod32MEL+pNwpsqmi4oB3IFIk9d5+Qr2UxqECDHofQ==", "cpu": [ "x64" ], @@ -1825,9 +1540,9 @@ ] }, "node_modules/@matrixai/quic-linux-x64": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/@matrixai/quic-linux-x64/-/quic-linux-x64-0.0.18.tgz", - "integrity": "sha512-i3cVTDjSI/VwZCQbjkToj98lGAoQAfhSLnFMt2OEoIlSn2CbBODpFsakodW3wvawItW+vwqMsucNktWQhBB10A==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@matrixai/quic-linux-x64/-/quic-linux-x64-0.1.3.tgz", + "integrity": "sha512-IITQegE+3dnhaEvmpnnR9iE7Unoy9TDmzL5cxj9oIb9VJPgga1QDci8MWavO9H78LrrYXy4hPI7zVH0NZ7SEPA==", "cpu": [ "x64" ], @@ -1851,12 +1566,12 @@ } }, "node_modules/@matrixai/workers": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@matrixai/workers/-/workers-1.3.7.tgz", - "integrity": "sha512-37Zm8OqrhLzcPY8uBWBhleN0THOxC49SwtkqTw8Ettb/Csm51MlGoa05NJa0NcBCsYfPucLK/qn1yFIGFrqWhw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@matrixai/workers/-/workers-1.4.0.tgz", + "integrity": "sha512-2WPVPChVWNPFBoabd/4/46kLhe2cnwP9yx1h9D4+Fj9Ctm4r9h+AvnB1jAJ1OgKDTRl22WNsZDfa6Aj1cyzI0Q==", "dependencies": { - "@matrixai/async-init": "^1.8.4", - "@matrixai/errors": "^1.1.7", + "@matrixai/async-init": "^1.9.1", + "@matrixai/errors": "^1.2.0", "@matrixai/logger": "^3.1.0", "threads": "^1.6.5" } @@ -2036,15 +1751,15 @@ } }, "node_modules/@peculiar/webcrypto": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.4.0.tgz", - "integrity": "sha512-U58N44b2m3OuTgpmKgf0LPDOmP3bhwNz01vAnj1mBwxBASRhptWYK+M3zG+HBkDqGQM+bFsoIihTW8MdmPXEqg==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.4.3.tgz", + "integrity": "sha512-VtaY4spKTdN5LjJ04im/d/joXuvLbQdgy5Z4DXF4MFZhQ+MTrejbNMkfZBp1Bs3O5+bFqnJgyGdPuZQflvIa5A==", "dependencies": { - "@peculiar/asn1-schema": "^2.1.6", + "@peculiar/asn1-schema": "^2.3.6", "@peculiar/json-schema": "^1.1.12", "pvtsutils": "^1.3.2", - "tslib": "^2.4.0", - "webcrypto-core": "^1.7.4" + "tslib": "^2.5.0", + "webcrypto-core": "^1.7.7" }, "engines": { "node": ">=10.12.0" @@ -2068,6 +1783,26 @@ "tsyringe": "^4.7.0" } }, + "node_modules/@pkgr/utils": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", + "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "fast-glob": "^3.3.0", + "is-glob": "^4.0.3", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@scure/base": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", @@ -2102,7 +1837,6 @@ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", "dev": true, - "peer": true, "dependencies": { "type-detect": "4.0.8" } @@ -2112,7 +1846,6 @@ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, - "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.0" } @@ -2123,11 +1856,14 @@ "integrity": "sha512-buWyDbFht82G2Dgt8yS1AiR12Y7uvgQv+wOY6X98Pattq95RyJp7wJp0zxDdI/6jqKSlHKwGJNX7KjrSnYHbOQ==" }, "node_modules/@swc/core": { - "version": "1.3.74", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.74.tgz", - "integrity": "sha512-P+MIExOTdWlfq8Heb1/NhBAke6UTckd4cRDuJoFcFMGBRvgoCMNWhnfP3FRRXPLI7GGg27dRZS+xHiqYyQmSrA==", + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.82.tgz", + "integrity": "sha512-jpC1a18HMH67018Ij2jh+hT7JBFu7ZKcQVfrZ8K6JuEY+kjXmbea07P9MbQUZbAe0FB+xi3CqEVCP73MebodJQ==", "dev": true, "hasInstallScript": true, + "dependencies": { + "@swc/types": "^0.1.4" + }, "engines": { "node": ">=10" }, @@ -2136,16 +1872,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.3.74", - "@swc/core-darwin-x64": "1.3.74", - "@swc/core-linux-arm-gnueabihf": "1.3.74", - "@swc/core-linux-arm64-gnu": "1.3.74", - "@swc/core-linux-arm64-musl": "1.3.74", - "@swc/core-linux-x64-gnu": "1.3.74", - "@swc/core-linux-x64-musl": "1.3.74", - "@swc/core-win32-arm64-msvc": "1.3.74", - "@swc/core-win32-ia32-msvc": "1.3.74", - "@swc/core-win32-x64-msvc": "1.3.74" + "@swc/core-darwin-arm64": "1.3.82", + "@swc/core-darwin-x64": "1.3.82", + "@swc/core-linux-arm-gnueabihf": "1.3.82", + "@swc/core-linux-arm64-gnu": "1.3.82", + "@swc/core-linux-arm64-musl": "1.3.82", + "@swc/core-linux-x64-gnu": "1.3.82", + "@swc/core-linux-x64-musl": "1.3.82", + "@swc/core-win32-arm64-msvc": "1.3.82", + "@swc/core-win32-ia32-msvc": "1.3.82", + "@swc/core-win32-x64-msvc": "1.3.82" }, "peerDependencies": { "@swc/helpers": "^0.5.0" @@ -2157,9 +1893,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.3.74", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.74.tgz", - "integrity": "sha512-2rMV4QxM583jXcREfo0MhV3Oj5pgRSfSh/kVrB1twL2rQxOrbzkAPT/8flmygdVoL4f2F7o1EY5lKlYxEBiIKQ==", + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.82.tgz", + "integrity": "sha512-JfsyDW34gVKD3uE0OUpUqYvAD3yseEaicnFP6pB292THtLJb0IKBBnK50vV/RzEJtc1bR3g1kNfxo2PeurZTrA==", "cpu": [ "arm64" ], @@ -2173,9 +1909,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.3.74", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.74.tgz", - "integrity": "sha512-KKEGE1wXneYXe15fWDRM8/oekd/Q4yAuccA0vWY/7i6nOSPqWYcSDR0nRtR030ltDxWt0rk/eCTmNkrOWrKs3A==", + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.82.tgz", + "integrity": "sha512-ogQWgNMq7qTpITjcP3dnzkFNj7bh6SwMr859GvtOTrE75H7L7jDWxESfH4f8foB/LGxBKiDNmxKhitCuAsZK4A==", "cpu": [ "x64" ], @@ -2189,9 +1925,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.3.74", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.74.tgz", - "integrity": "sha512-HehH5DR6r/5fIVu7tu8ZqgrHkhSCQNewf1ztFQJgcmaQWn+H4AJERBjwkjosqh4TvUJucZv8vyRTvrFeBXaCSA==", + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.82.tgz", + "integrity": "sha512-7TMXG1lXlNhD0kUiEqs+YlGV4irAdBa2quuy+XI3oJf2fBK6dQfEq4xBy65B3khrorzQS3O0oDGQ+cmdpHExHA==", "cpu": [ "arm" ], @@ -2205,9 +1941,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.3.74", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.74.tgz", - "integrity": "sha512-+xkbCRz/wczgdknoV4NwYxbRI2dD7x/qkIFcVM2buzLCq8oWLweuV8+aL4pRqu0qDh7ZSb1jcaVTUIsySCJznA==", + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.82.tgz", + "integrity": "sha512-26JkOujbzcItPAmIbD5vHJxQVy5ihcSu3YHTKwope1h28sApZdtE7S3e2G3gsZRTIdsCQkXUtAQeqHxGWWR3pw==", "cpu": [ "arm64" ], @@ -2221,9 +1957,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.3.74", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.74.tgz", - "integrity": "sha512-maKFZSCD3tQznzPV7T3V+TtiWZFEFM8YrnSS5fQNNb+K9J65sL+170uTb3M7H4cFkG+9Sm5k5yCrCIutlvV48g==", + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.82.tgz", + "integrity": "sha512-8Izj9tuuMpoc3cqiPBRtwqpO1BZ/+sfZVsEhLxrbOFlcSb8LnKyMle1g3JMMUwI4EU75RGVIzZMn8A6GOKdJbA==", "cpu": [ "arm64" ], @@ -2237,9 +1973,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.3.74", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.74.tgz", - "integrity": "sha512-LEXpcShF6DLTWJSiBhMSYZkLQ27UvaQ24fCFhoIV/R3dhYaUpHmIyLPPBNC82T03lB3ONUFVwrRw6fxDJ/f00A==", + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.82.tgz", + "integrity": "sha512-0GSrIBScQwTaPv46T2qB7XnDYxndRCpwH4HMjh6FN+I+lfPUhTSJKW8AonqrqT1TbpFIgvzQs7EnTsD7AnSCow==", "cpu": [ "x64" ], @@ -2253,9 +1989,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.3.74", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.74.tgz", - "integrity": "sha512-sxsFctbFMZEFmDE7CmYljG0dMumH8XBTwwtGr8s6z0fYAzXBGNq2AFPcmEh2np9rPWkt7pE1m0ByESD+dMkbxQ==", + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.82.tgz", + "integrity": "sha512-KJUnaaepDKNzrEbwz4jv0iC3/t9x0NSoe06fnkAlhh2+NFKWKKJhVCOBTrpds8n7eylBDIXUlK34XQafjVMUdg==", "cpu": [ "x64" ], @@ -2269,9 +2005,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.3.74", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.74.tgz", - "integrity": "sha512-F7hY9/BjFCozA4YPFYFH5FGCyWwa44vIXHqG66F5cDwXDGFn8ZtBsYIsiPfUYcx0AeAo1ojnVWKPxokZhYNYqA==", + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.82.tgz", + "integrity": "sha512-TR3MHKhDYIyGyFcyl2d/p1ftceXcubAhX5wRSOdtOyr5+K/v3jbyCCqN7bbqO5o43wQVCwwR/drHleYyDZvg8Q==", "cpu": [ "arm64" ], @@ -2285,9 +2021,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.3.74", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.74.tgz", - "integrity": "sha512-qBAsiD1AlIdqED6wy3UNRHyAys9pWMUidX0LJ6mj24r/vfrzzTBAUrLJe5m7bzE+F1Rgi001avYJeEW1DLEJ+Q==", + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.82.tgz", + "integrity": "sha512-ZX4HzVVt6hs84YUg70UvyBJnBOIspmQQM0iXSzBvOikk3zRoN7BnDwQH4GScvevCEBuou60+i4I6d5kHLOfh8Q==", "cpu": [ "ia32" ], @@ -2301,9 +2037,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.3.74", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.74.tgz", - "integrity": "sha512-S3YAvvLprTnPRwQuy9Dkwubb5SRLpVK3JJsqYDbGfgj8PGQyKHZcVJ5X3nfFsoWLy3j9B/3Os2nawprRSzeC5A==", + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.82.tgz", + "integrity": "sha512-4mJMnex21kbQoaHeAmHnVwQN9/XAfPszJ6n9HI7SVH+aAHnbBIR0M59/b50/CJMjTj5niUGk7EwQ3nhVNOG32g==", "cpu": [ "x64" ], @@ -2317,9 +2053,9 @@ } }, "node_modules/@swc/jest": { - "version": "0.2.28", - "resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.28.tgz", - "integrity": "sha512-iCB3lvngkQldLga35krb8LPa+6gmkVXnlpfCTXOAgMaEYFagLxOIFbIO8II7dhHa8ApOv5ap8iFRETI4lVY0vw==", + "version": "0.2.29", + "resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.29.tgz", + "integrity": "sha512-8reh5RvHBsSikDC3WGCd5ZTd2BXKkyOdK7QwynrCH58jk2cQFhhHhFBg/jvnWZehUQe/EoOImLENc9/DwbBFow==", "dev": true, "dependencies": { "@jest/create-cache-key-function": "^27.4.2", @@ -2332,6 +2068,12 @@ "@swc/core": "*" } }, + "node_modules/@swc/types": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.4.tgz", + "integrity": "sha512-z/G02d+59gyyUb7KYhKi9jOhicek6QD2oMaotUyG+lUkybpXoV49dY9bj7Ah5Q+y7knK2jU67UTX9FyfGzaxQg==", + "dev": true + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -2440,144 +2182,31 @@ } }, "node_modules/@types/jest": { - "version": "28.1.8", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz", - "integrity": "sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==", + "version": "29.5.4", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.4.tgz", + "integrity": "sha512-PhglGmhWeD46FYOVLt3X7TiWjzwuVGW9wG/4qocPevXMjCmrIc5b6db9WjeGE4QYVpUAWMDv3v0IiBwObY289A==", "dev": true, "dependencies": { - "expect": "^28.0.0", - "pretty-format": "^28.0.0" + "expect": "^29.0.0", + "pretty-format": "^29.0.0" } }, - "node_modules/@types/jest/node_modules/@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "dependencies": { - "jest-get-type": "^28.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true }, - "node_modules/@types/jest/node_modules/diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@types/jest/node_modules/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@types/jest/node_modules/jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@types/jest/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@types/jest/node_modules/jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@types/jest/node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@types/jest/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", - "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/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/node": { - "version": "18.17.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.3.tgz", - "integrity": "sha512-2x8HWtFk0S99zqVQABU9wTpr8wPoaDHZUcAkoTKH+nL7kPv3WUI9cRi/Kk5Mz4xdqXSqTkKP7IWNoQQYCnDsTA==" + "version": "20.5.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.9.tgz", + "integrity": "sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==" }, "node_modules/@types/pako": { "version": "1.0.4", @@ -2585,12 +2214,6 @@ "integrity": "sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA==", "dev": true }, - "node_modules/@types/prettier": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", - "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", - "dev": true - }, "node_modules/@types/prompts": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.4.4.tgz", @@ -3132,21 +2755,21 @@ } }, "node_modules/babel-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", - "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.4.tgz", + "integrity": "sha512-meLj23UlSLddj6PC+YTOFRgDAtjnZom8w/ACsrx0gtPtv5cJZk0A5Unk5bV4wixD7XaPCN1fQvpww8czkZURmw==", "dev": true, "dependencies": { - "@jest/transform": "^28.1.3", + "@jest/transform": "^29.6.4", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^28.1.3", + "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" @@ -3169,9 +2792,9 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", - "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, "dependencies": { "@babel/template": "^7.3.3", @@ -3180,7 +2803,7 @@ "@types/babel__traverse": "^7.0.6" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/babel-preset-current-node-syntax": { @@ -3207,16 +2830,16 @@ } }, "node_modules/babel-preset-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", - "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, "dependencies": { - "babel-plugin-jest-hoist": "^28.1.3", + "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" @@ -3276,6 +2899,15 @@ "node": ">=6" } }, + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/bitset": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bitset/-/bitset-5.1.1.tgz", @@ -3289,6 +2921,18 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, + "node_modules/bplist-parser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", + "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.44" + }, + "engines": { + "node": ">= 5.10.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3370,6 +3014,21 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/bundle-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", + "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", + "dev": true, + "dependencies": { + "run-applescript": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -3711,10 +3370,18 @@ } }, "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 + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } }, "node_modules/deep-is": { "version": "0.1.4", @@ -3731,6 +3398,162 @@ "node": ">=0.10.0" } }, + "node_modules/default-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", + "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", + "dev": true, + "dependencies": { + "bundle-name": "^3.0.0", + "default-browser-id": "^3.0.0", + "execa": "^7.1.1", + "titleize": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", + "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", + "dev": true, + "dependencies": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/default-browser/node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/default-browser/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-properties": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", @@ -3765,9 +3588,9 @@ } }, "node_modules/diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3860,9 +3683,9 @@ "dev": true }, "node_modules/emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, "engines": { "node": ">=12" @@ -4220,21 +4043,29 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz", + "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==", "dev": true, "dependencies": { - "prettier-linter-helpers": "^1.0.0" + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.5" }, "engines": { - "node": ">=12.0.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/prettier" }, "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "prettier": ">=3.0.0" }, "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, "eslint-config-prettier": { "optional": true } @@ -4444,18 +4275,16 @@ } }, "node_modules/expect": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.2.tgz", - "integrity": "sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.4.tgz", + "integrity": "sha512-F2W2UyQ8XYyftHT57dtfg8Ue3X5qLgm2sSug0ivvLRH/VKNRL/pDxg/TH7zVzbQB0tu80clNFy6LU7OS/VSEKA==", "dev": true, - "peer": true, "dependencies": { - "@jest/expect-utils": "^29.6.2", - "@types/node": "*", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.6.2", - "jest-message-util": "^29.6.2", - "jest-util": "^29.6.2" + "@jest/expect-utils": "^29.6.4", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.6.4", + "jest-message-util": "^29.6.3", + "jest-util": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -4658,9 +4487,9 @@ "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==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, "optional": true, @@ -5174,6 +5003,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5213,6 +5057,24 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -5358,6 +5220,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/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, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-wsl/node_modules/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, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -5482,21 +5371,21 @@ "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" }, "node_modules/jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", - "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.4.tgz", + "integrity": "sha512-tEFhVQFF/bzoYV1YuGyzLPZ6vlPrdfvDmmAxudA1dLEuiztqg2Rkx20vkKY32xiDROcD2KXlgZ7Cu8RPeEHRKw==", "dev": true, "dependencies": { - "@jest/core": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/core": "^29.6.4", + "@jest/types": "^29.6.3", "import-local": "^3.0.2", - "jest-cli": "^28.1.3" + "jest-cli": "^29.6.4" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -5508,287 +5397,66 @@ } }, "node_modules/jest-changed-files": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", - "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.6.3.tgz", + "integrity": "sha512-G5wDnElqLa4/c66ma5PG9eRjE342lIbF6SUnTJi26C3J28Fv2TVY2rOyKB9YGbSA5ogwevgmxc4j4aVjrEK6Yg==", "dev": true, "dependencies": { "execa": "^5.0.0", + "jest-util": "^29.6.3", "p-limit": "^3.1.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-circus": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", - "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.4.tgz", + "integrity": "sha512-YXNrRyntVUgDfZbjXWBMPslX1mQ8MrSG0oM/Y06j9EYubODIyHWP8hMUbjbZ19M3M+zamqEur7O80HODwACoJw==", "dev": true, "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/environment": "^29.6.4", + "@jest/expect": "^29.6.4", + "@jest/test-result": "^29.6.4", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "dedent": "^0.7.0", + "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", - "jest-each": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", + "jest-each": "^29.6.3", + "jest-matcher-utils": "^29.6.4", + "jest-message-util": "^29.6.3", + "jest-runtime": "^29.6.4", + "jest-snapshot": "^29.6.4", + "jest-util": "^29.6.3", "p-limit": "^3.1.0", - "pretty-format": "^28.1.3", + "pretty-format": "^29.6.3", + "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "node_modules/jest-cli": { + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.4.tgz", + "integrity": "sha512-+uMCQ7oizMmh8ZwRfZzKIEszFY9ksjjEQnTEMTaL7fYiL3Kw4XhqT9bYh+A4DQKUb67hZn2KbtEnDuHvcgK4pQ==", "dev": true, "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/@jest/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", - "dev": true, - "dependencies": { - "expect": "^28.1.3", - "jest-snapshot": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "dependencies": { - "jest-get-type": "^28.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/jest-circus/node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/jest-circus/node_modules/diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-snapshot": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", - "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-haste-map": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "natural-compare": "^1.4.0", - "pretty-format": "^28.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-cli": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", - "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", - "dev": true, - "dependencies": { - "@jest/core": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/core": "^29.6.4", + "@jest/test-result": "^29.6.4", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", + "jest-config": "^29.6.4", + "jest-util": "^29.6.3", + "jest-validate": "^29.6.3", "prompts": "^2.0.1", "yargs": "^17.3.1" }, @@ -5796,7 +5464,7 @@ "jest": "bin/jest.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -5807,54 +5475,37 @@ } } }, - "node_modules/jest-cli/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, "node_modules/jest-config": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", - "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.4.tgz", + "integrity": "sha512-JWohr3i9m2cVpBumQFv2akMEnFEPVOh+9L2xIBJhJ0zOaci2ZXuKJj0tgMKQCBZAKA09H049IR4HVS/43Qb19A==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^28.1.3", - "@jest/types": "^28.1.3", - "babel-jest": "^28.1.3", + "@jest/test-sequencer": "^29.6.4", + "@jest/types": "^29.6.3", + "babel-jest": "^29.6.4", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^28.1.3", - "jest-environment-node": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", + "jest-circus": "^29.6.4", + "jest-environment-node": "^29.6.4", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.6.4", + "jest-runner": "^29.6.4", + "jest-util": "^29.6.3", + "jest-validate": "^29.6.3", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^28.1.3", + "pretty-format": "^29.6.3", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@types/node": "*", @@ -5869,248 +5520,70 @@ } } }, - "node_modules/jest-config/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-config/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, "node_modules/jest-diff": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.2.tgz", - "integrity": "sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.4.tgz", + "integrity": "sha512-9F48UxR9e4XOEZvoUXEHSWY4qC4zERJaOfrbBg9JpbJOO43R1vN76REt/aMGZoY6GD5g84nnJiBIVlscegefpw==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.6.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-diff/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/jest-diff/node_modules/pretty-format": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", - "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-docblock": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", - "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.6.3.tgz", + "integrity": "sha512-2+H+GOTQBEm2+qFSQ7Ma+BvyV+waiIFxmZF5LdpBsAEjWX8QYjSCa4FrkIYtbfXUJJJnFCYrOtt6TZ+IAiTjBQ==", "dev": true, "dependencies": { "detect-newline": "^3.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-each": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", - "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "jest-util": "^28.1.3", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-each/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-each/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.3.tgz", + "integrity": "sha512-KoXfJ42k8cqbkfshW7sSHcdfnv5agDdHCPA87ZBdmHP+zJstTJc0ttQaJ/x7zK6noAL76hOuTIJ6ZkQRS5dcyg==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "jest-get-type": "^29.6.3", + "jest-util": "^29.6.3", + "pretty-format": "^29.6.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", - "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/jest-environment-node/node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/jest-environment-node/node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.4.tgz", + "integrity": "sha512-i7SbpH2dEIFGNmxGCpSc2w9cA4qVD+wfvg2ZnfQ7XVrKL0NA5uDVBIiGH8SR4F0dKEv/0qI5r+aDomDf04DpEQ==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", + "@jest/environment": "^29.6.4", + "@jest/fake-timers": "^29.6.4", + "@jest/types": "^29.6.3", "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "jest-mock": "^29.6.3", + "jest-util": "^29.6.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-extended": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/jest-extended/-/jest-extended-3.2.4.tgz", - "integrity": "sha512-lSEYhSmvXZG/7YXI7KO3LpiUiQ90gi5giwCJNDMMsX5a+/NZhdbQF2G4ALOBN+KcXVT3H6FPVPohAuMXooaLTQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jest-extended/-/jest-extended-4.0.1.tgz", + "integrity": "sha512-KM6dwuBUAgy6QONuR19CGubZB9Hkjqvl/d5Yc/FXsdB8+gsGxB2VQ+NEdOrr95J4GMPeLnDoPOKyi6+mKCCnZQ==", "dev": true, "dependencies": { "jest-diff": "^29.0.0", @@ -6129,1105 +5602,315 @@ } }, "node_modules/jest-get-type": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-haste-map": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", - "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.4.tgz", + "integrity": "sha512-12Ad+VNTDHxKf7k+M65sviyynRoZYuL1/GTuhEVb8RYsNSNln71nANRb/faSyWvx0j+gHcivChXHIoMJrGYjog==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", + "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.6.3", + "jest-worker": "^29.6.4", "micromatch": "^4.0.4", "walker": "^1.0.8" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "optionalDependencies": { "fsevents": "^2.3.2" } - }, - "node_modules/jest-haste-map/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-junit": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-14.0.1.tgz", - "integrity": "sha512-h7/wwzPbllgpQhhVcRzRC76/cc89GlazThoV1fDxcALkf26IIlRsu/AcTG64f4nR2WPE3Cbd+i/sVf+NCUHrWQ==", - "dev": true, - "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": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", - "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", - "dev": true, - "dependencies": { - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz", - "integrity": "sha512-4LiAk3hSSobtomeIAzFTe+N8kL6z0JtF3n6I4fg29iIW7tt99R7ZcIFW34QkX+DuVrf+CUe6wuVOpm7ZKFJzZQ==", - "dev": true, - "peer": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.6.2", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.6.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils/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, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", - "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", - "dev": true, - "peer": true, - "dependencies": { - "@jest/schemas": "^29.6.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.2.tgz", - "integrity": "sha512-vnIGYEjoPSuRqV8W9t+Wow95SDp6KPX2Uf7EoeG9G99J2OVh7OSwpS4B6J0NfpEIpfkBNHlBZpA2rblEuEFhZQ==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.6.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/@jest/types": { - "version": "29.6.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", - "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", - "dev": true, - "peer": true, - "dependencies": { - "@jest/schemas": "^29.6.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/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, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", - "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", - "dev": true, - "peer": true, - "dependencies": { - "@jest/schemas": "^29.6.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.2.tgz", - "integrity": "sha512-hoSv3lb3byzdKfwqCuT6uTscan471GUECqgNYykg6ob0yiAw3zYc7OrPnI9Qv8Wwoa4lC7AZ9hyS4AiIx5U2zg==", - "dev": true, - "peer": true, - "dependencies": { - "@jest/types": "^29.6.1", - "@types/node": "*", - "jest-util": "^29.6.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "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-mock/node_modules/@jest/types": { - "version": "29.6.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", - "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", - "dev": true, - "peer": true, - "dependencies": { - "@jest/schemas": "^29.6.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", - "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", - "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", - "dev": true, - "dependencies": { - "jest-regex-util": "^28.0.2", - "jest-snapshot": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "dependencies": { - "jest-get-type": "^28.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/jest-snapshot": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", - "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-haste-map": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "natural-compare": "^1.4.0", - "pretty-format": "^28.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runner": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", - "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", - "dev": true, - "dependencies": { - "@jest/console": "^28.1.3", - "@jest/environment": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "graceful-fs": "^4.2.9", - "jest-docblock": "^28.1.1", - "jest-environment-node": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-leak-detector": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-resolve": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-util": "^28.1.3", - "jest-watcher": "^28.1.3", - "jest-worker": "^28.1.3", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runner/node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runner/node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runner/node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/jest-runner/node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/jest-runner/node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runner/node_modules/jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runner/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", - "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/globals": "^28.1.3", - "@jest/source-map": "^28.1.2", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "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": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@jest/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", - "dev": true, - "dependencies": { - "expect": "^28.1.3", - "jest-snapshot": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "dependencies": { - "jest-get-type": "^28.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@jest/globals": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", - "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/types": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/jest-runtime/node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/jest-runtime/node_modules/diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/jest-snapshot": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", - "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-haste-map": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "natural-compare": "^1.4.0", - "pretty-format": "^28.1.3", - "semver": "^7.3.5" + }, + "node_modules/jest-junit": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz", + "integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": ">=10.12.0" } }, - "node_modules/jest-runtime/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "node_modules/jest-leak-detector": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.3.tgz", + "integrity": "sha512-0kfbESIHXYdhAdpLsW7xdwmYhLf1BRu4AA118/OxFm0Ho1b2RcTmO4oF6aAMaxpxdxnJ3zve2rgwzNBD4Zbm7Q==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.6.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.2.tgz", - "integrity": "sha512-1OdjqvqmRdGNvWXr/YZHuyhh5DeaLp1p/F8Tht/MrMw4Kr1Uu/j4lRG+iKl1DAqUJDWxtQBMk41Lnf/JETYBRA==", + "node_modules/jest-matcher-utils": { + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.4.tgz", + "integrity": "sha512-KSzwyzGvK4HcfnserYqJHYi7sZVqdREJ9DMPAKVbS98JsIAvumihaNUbjrWw0St7p9IY7A9UskCW5MYlGmBQFQ==", "dev": true, - "peer": true, "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.6.2", - "@jest/transform": "^29.6.2", - "@jest/types": "^29.6.1", - "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.6.2", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.6.2", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.6.2", - "jest-message-util": "^29.6.2", - "jest-util": "^29.6.2", - "natural-compare": "^1.4.0", - "pretty-format": "^29.6.2", - "semver": "^7.5.3" + "jest-diff": "^29.6.4", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/@jest/transform": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.2.tgz", - "integrity": "sha512-ZqCqEISr58Ce3U+buNFJYUktLJZOggfyvR+bZMaiV1e8B1SIvJbwZMrYz3gx/KAPn9EXmOmN+uB08yLCjWkQQg==", + "node_modules/jest-message-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.3.tgz", + "integrity": "sha512-FtzaEEHzjDpQp51HX4UMkPZjy46ati4T5pEMyM6Ik48ztu4T9LQplZ6OsimHx7EuM9dfEh5HJa6D3trEftu3dA==", "dev": true, - "peer": true, "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.1", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.2", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.6.2", "micromatch": "^4.0.4", - "pirates": "^4.0.4", + "pretty-format": "^29.6.3", "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "stack-utils": "^2.0.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/@jest/types": { - "version": "29.6.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", - "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", + "node_modules/jest-mock": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.3.tgz", + "integrity": "sha512-Z7Gs/mOyTSR4yPsaZ72a/MtuK6RnC3JYqWONe48oLaoEcYwEDxqvbXz85G4SJrm2Z5Ar9zp6MiHF4AlFlRM4Pg==", "dev": true, - "peer": true, "dependencies": { - "@jest/schemas": "^29.6.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "jest-util": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/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==", + "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, - "peer": true, "engines": { - "node": ">=10" + "node": ">=8.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "jest": ">=24.0.0" } }, - "node_modules/jest-snapshot/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "peer": true - }, - "node_modules/jest-snapshot/node_modules/jest-haste-map": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.2.tgz", - "integrity": "sha512-+51XleTDAAysvU8rT6AnS1ZJ+WHVNqhj1k6nTvN2PYP+HjU3kqlaKQ1Lnw3NYW3bm2r8vq82X0Z1nDDHZMzHVA==", + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, - "peer": true, - "dependencies": { - "@jest/types": "^29.6.1", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.6.2", - "jest-worker": "^29.6.2", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6" }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } } }, - "node_modules/jest-snapshot/node_modules/jest-regex-util": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, - "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/jest-worker": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.2.tgz", - "integrity": "sha512-l3ccBOabTdkng8I/ORCkADz4eSMKejTYv1vB/Z83UiubqhC1oQ5Li6dWCyqOIvSifGjUBxuvxvlm6KGK2DtuAQ==", + "node_modules/jest-resolve": { + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.4.tgz", + "integrity": "sha512-fPRq+0vcxsuGlG0O3gyoqGTAxasagOxEuyoxHeyxaZbc9QNek0AmJWSkhjlMG+mTsj+8knc/mWb3fXlRNVih7Q==", "dev": true, - "peer": true, "dependencies": { - "@types/node": "*", - "jest-util": "^29.6.2", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.6.3", + "jest-validate": "^29.6.3", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/pretty-format": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", - "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", + "node_modules/jest-resolve-dependencies": { + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.4.tgz", + "integrity": "sha512-7+6eAmr1ZBF3vOAJVsfLj1QdqeXG+WYhidfLHBRZqGN24MFRIiKG20ItpLw2qRAsW/D2ZUUmCNf6irUr/v6KHA==", "dev": true, - "peer": true, "dependencies": { - "@jest/schemas": "^29.6.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.6.4" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/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==", + "node_modules/jest-runner": { + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.4.tgz", + "integrity": "sha512-SDaLrMmtVlQYDuG0iSPYLycG8P9jLI+fRm8AF/xPKhYDB2g6xDWjXBrR5M8gEWsK6KVFlebpZ4QsrxdyIX1Jaw==", "dev": true, - "peer": true, "dependencies": { - "has-flag": "^4.0.0" + "@jest/console": "^29.6.4", + "@jest/environment": "^29.6.4", + "@jest/test-result": "^29.6.4", + "@jest/transform": "^29.6.4", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.6.3", + "jest-environment-node": "^29.6.4", + "jest-haste-map": "^29.6.4", + "jest-leak-detector": "^29.6.3", + "jest-message-util": "^29.6.3", + "jest-resolve": "^29.6.4", + "jest-runtime": "^29.6.4", + "jest-util": "^29.6.3", + "jest-watcher": "^29.6.4", + "jest-worker": "^29.6.4", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.4.tgz", + "integrity": "sha512-s/QxMBLvmwLdchKEjcLfwzP7h+jsHvNEtxGP5P+Fl1FMaJX2jMiIqe4rJw4tFprzCwuSvVUo9bn0uj4gNRXsbA==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.4", + "@jest/fake-timers": "^29.6.4", + "@jest/globals": "^29.6.4", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.6.4", + "@jest/transform": "^29.6.4", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.4", + "jest-message-util": "^29.6.3", + "jest-mock": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.6.4", + "jest-snapshot": "^29.6.4", + "jest-util": "^29.6.3", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.2.tgz", - "integrity": "sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w==", + "node_modules/jest-snapshot": { + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.4.tgz", + "integrity": "sha512-VC1N8ED7+4uboUKGIDsbvNAZb6LakgIPgAF4RSpF13dN6YaMokfRqO+BaqK4zIh6X3JffgwbzuGqDEjHm/MrvA==", "dev": true, - "peer": true, "dependencies": { - "@jest/types": "^29.6.1", - "@types/node": "*", + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.6.4", + "@jest/transform": "^29.6.4", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "ci-info": "^3.2.0", + "expect": "^29.6.4", "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "jest-diff": "^29.6.4", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.6.4", + "jest-message-util": "^29.6.3", + "jest-util": "^29.6.3", + "natural-compare": "^1.4.0", + "pretty-format": "^29.6.3", + "semver": "^7.5.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util/node_modules/@jest/types": { - "version": "29.6.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", - "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", + "node_modules/jest-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.3.tgz", + "integrity": "sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA==", "dev": true, - "peer": true, "dependencies": { - "@jest/schemas": "^29.6.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-validate": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", - "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.3.tgz", + "integrity": "sha512-e7KWZcAIX+2W1o3cHfnqpGajdCs1jSM3DkXjGeLSNmCazv1EeI1ggTeK5wdZhF+7N+g44JI2Od3veojoaumlfg==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", + "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", + "jest-get-type": "^29.6.3", "leven": "^3.1.0", - "pretty-format": "^28.1.3" + "pretty-format": "^29.6.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-validate/node_modules/camelcase": { @@ -7242,63 +5925,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-validate/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, "node_modules/jest-watcher": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", - "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.4.tgz", + "integrity": "sha512-oqUWvx6+On04ShsT00Ir9T4/FvBeEh2M9PTubgITPxDa739p4hoQweWPRGyYeaojgT0xTpZKF0Y/rSY1UgMxvQ==", "dev": true, "dependencies": { - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/test-result": "^29.6.4", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.3", + "emittery": "^0.13.1", + "jest-util": "^29.6.3", "string-length": "^4.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watcher/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-worker": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", - "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.4.tgz", + "integrity": "sha512-6dpvFV4WjcWbDVGgHTWo/aupl8/LbBx2NSKfiwqf79xC/yeJjKHT1+StcKy/2KTmW16hE68ccKVOtXf+WZGz7Q==", "dev": true, "dependencies": { "@types/node": "*", + "jest-util": "^29.6.3", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-worker/node_modules/supports-color": { @@ -7911,6 +6569,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "dev": true, + "dependencies": { + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -8181,15 +6857,15 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" @@ -8208,38 +6884,19 @@ } }, "node_modules/pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz", + "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==", "dev": true, "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/pretty-format/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/pretty-format/node_modules/@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", - "dev": true - }, "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", @@ -8451,9 +7108,9 @@ } }, "node_modules/resolve.exports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", - "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true, "engines": { "node": ">=10" @@ -8509,6 +7166,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/run-applescript": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", + "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -8974,19 +7646,6 @@ "node": ">=8" } }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.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", @@ -8999,6 +7658,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dev": true, + "dependencies": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/systeminformation": { "version": "5.18.12", "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.18.12.tgz", @@ -9025,22 +7700,6 @@ "url": "https://www.buymeacoffee.com/systeminfo" } }, - "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", @@ -9092,6 +7751,18 @@ "esm": "^3.2.25" } }, + "node_modules/titleize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", + "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -9133,32 +7804,32 @@ } }, "node_modules/ts-jest": { - "version": "28.0.8", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", - "integrity": "sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==", + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", "dev": true, "dependencies": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", - "jest-util": "^28.0.0", - "json5": "^2.2.1", + "jest-util": "^29.0.0", + "json5": "^2.2.3", "lodash.memoize": "4.x", "make-error": "1.x", - "semver": "7.x", + "semver": "^7.5.3", "yargs-parser": "^21.0.1" }, "bin": { "ts-jest": "cli.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^28.0.0", - "babel-jest": "^28.0.0", - "jest": "^28.0.0", - "typescript": ">=4.3" + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" }, "peerDependenciesMeta": { "@babel/core": { @@ -9175,23 +7846,6 @@ } } }, - "node_modules/ts-jest/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -9405,14 +8059,14 @@ } }, "node_modules/typedoc": { - "version": "0.23.28", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.28.tgz", - "integrity": "sha512-9x1+hZWTHEQcGoP7qFmlo4unUoVJLB0H/8vfO/7wqTnZxg4kPuji9y3uRzEu0ZKez63OJAUmiGhUrtukC6Uj3w==", + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz", + "integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==", "dev": true, "dependencies": { "lunr": "^2.3.9", - "marked": "^4.2.12", - "minimatch": "^7.1.3", + "marked": "^4.3.0", + "minimatch": "^9.0.0", "shiki": "^0.14.1" }, "bin": { @@ -9422,7 +8076,7 @@ "node": ">= 14.14" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x" + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x" } }, "node_modules/typedoc/node_modules/brace-expansion": { @@ -9435,31 +8089,31 @@ } }, "node_modules/typedoc/node_modules/minimatch": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", - "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/unbox-primitive": { @@ -9499,6 +8153,15 @@ "node": ">= 10.0.0" } }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", diff --git a/package.json b/package.json index b476776da..5eab09ed5 100644 --- a/package.json +++ b/package.json @@ -63,22 +63,23 @@ }, "dependencies": { "@matrixai/async-cancellable": "^1.1.1", - "@matrixai/async-init": "^1.8.4", + "@matrixai/async-init": "^1.10.0", "@matrixai/async-locks": "^4.0.0", "@matrixai/contexts": "^1.1.0", - "@matrixai/db": "^5.2.0", - "@matrixai/errors": "^1.1.7", + "@matrixai/db": "^5.3.0", + "@matrixai/errors": "^1.2.0", "@matrixai/id": "^3.3.6", "@matrixai/logger": "^3.1.0", "@matrixai/resources": "^1.1.5", "@matrixai/timer": "^1.1.1", "@matrixai/workers": "^1.3.7", - "@matrixai/quic": "^0.0.18", + "@matrixai/quic": "^0.1.3", + "@matrixai/events": "^3.2.0", "@peculiar/asn1-pkcs8": "^2.3.0", "@peculiar/asn1-schema": "^2.3.0", "@peculiar/asn1-x509": "^2.3.0", - "@peculiar/webcrypto": "1.4.0", - "@peculiar/x509": "1.8.3", + "@peculiar/webcrypto": "^1.4.3", + "@peculiar/x509": "^1.8.3", "@scure/bip39": "^1.1.0", "@streamparser/json": "^0.0.13", "@types/ws": "^8.5.4", @@ -105,36 +106,36 @@ }, "devDependencies": { "@fast-check/jest": "^1.1.0", - "@swc/core": "^1.3.62", - "@swc/jest": "^0.2.26", + "@swc/core": "1.3.82", + "@swc/jest": "^0.2.29", "@types/cross-spawn": "^6.0.2", - "@types/jest": "^28.1.3", - "@types/node": "^18.15.0", + "@types/jest": "^29.5.2", + "@types/node": "^20.5.7", "@types/pako": "^1.0.2", "@types/prompts": "^2.0.13", "@types/readable-stream": "^2.3.11", - "@typescript-eslint/eslint-plugin": "^5.45.1", - "@typescript-eslint/parser": "^5.45.1", + "@typescript-eslint/eslint-plugin": "^5.61.0", + "@typescript-eslint/parser": "^5.61.0", "benny": "^3.7.1", "common-tags": "^1.8.2", - "eslint": "^8.15.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-prettier": "^4.0.0", + "eslint": "^8.44.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-prettier": "^5.0.0-alpha.2", "fast-check": "^3.0.1", - "jest": "^28.1.1", - "jest-extended": "^3.0.1", - "jest-junit": "^14.0.0", + "jest": "^29.6.2", + "jest-extended": "^4.0.0", + "jest-junit": "^16.0.0", "jest-mock-props": "^1.9.1", "node-gyp-build": "^4.4.0", - "prettier": "^2.6.2", + "prettier": "^3.0.0", "shelljs": "^0.8.5", "shx": "^0.3.4", "systeminformation": "^5.18.5", - "ts-jest": "^28.0.5", + "ts-jest": "^29.1.1", "ts-node": "^10.9.1", "tsconfig-paths": "^3.9.0", - "typedoc": "^0.23.21", - "typescript": "^4.9.3" + "typedoc": "^0.24.8", + "typescript": "^5.1.6" } } diff --git a/pkgs.nix b/pkgs.nix index bb501409e..2997ae2e1 100644 --- a/pkgs.nix +++ b/pkgs.nix @@ -1,4 +1,4 @@ import ( - let rev = "f294325aed382b66c7a188482101b0f336d1d7db"; in + let rev = "ea5234e7073d5f44728c499192544a84244bf35a"; in builtins.fetchTarball "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz" ) diff --git a/shell.nix b/shell.nix index ce41038b6..2f0d85dd6 100644 --- a/shell.nix +++ b/shell.nix @@ -3,7 +3,7 @@ with pkgs; mkShell { nativeBuildInputs = [ - nodejs + nodejs_20 shellcheck gitAndTools.gh jq diff --git a/src/EventPolykey.ts b/src/EventPolykey.ts new file mode 100644 index 000000000..b668f5ef6 --- /dev/null +++ b/src/EventPolykey.ts @@ -0,0 +1,5 @@ +import { AbstractEvent } from '@matrixai/events'; + +abstract class EventPolykey extends AbstractEvent {} + +export default EventPolykey; diff --git a/src/PolykeyAgent.ts b/src/PolykeyAgent.ts index 6ead0d885..3081108f7 100644 --- a/src/PolykeyAgent.ts +++ b/src/PolykeyAgent.ts @@ -1,24 +1,18 @@ -import type { FileSystem, PromiseDeconstructed } from './types'; +import type { DeepPartial, FileSystem, PromiseDeconstructed } from './types'; import type { PolykeyWorkerManagerInterface } from './workers/types'; -import type { ConnectionData, TLSConfig } from './network/types'; -import type { SeedNodes } from './nodes/types'; -import type { CertManagerChangeData, Key } from './keys/types'; -import type { RecoveryCode, PrivateKey } from './keys/types'; -import type { PasswordMemLimit, PasswordOpsLimit } from './keys/types'; -import type { ClientCrypto, ServerCrypto } from '@matrixai/quic'; +import type { TLSConfig } from './network/types'; +import type { SeedNodes, NodesOptions } from './nodes/types'; +import type { Key, KeysOptions } from './keys/types'; import path from 'path'; import process from 'process'; -import { webcrypto } from 'crypto'; import Logger from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { CreateDestroyStartStop } from '@matrixai/async-init/dist/CreateDestroyStartStop'; -import { QUICServer, QUICSocket } from '@matrixai/quic'; import RPCServer from './rpc/RPCServer'; import WebSocketServer from './websockets/WebSocketServer'; import * as rpcUtilsMiddleware from './rpc/utils/middleware'; import * as clientUtilsMiddleware from './client/utils/middleware'; import { WorkerManager } from './workers'; -import * as networkUtils from './network/utils'; import KeyRing from './keys/KeyRing'; import CertManager from './keys/CertManager'; import Status from './status/Status'; @@ -35,137 +29,89 @@ import Discovery from './discovery/Discovery'; import SessionManager from './sessions/SessionManager'; import IdentitiesManager from './identities/IdentitiesManager'; import { providers } from './identities'; -import { EventBus, captureRejectionSymbol } from './events'; import config from './config'; import * as errors from './errors'; +import * as events from './events'; import * as utils from './utils'; import * as keysUtils from './keys/utils'; +import * as keysEvents from './keys/events'; import * as nodesUtils from './nodes/utils'; import * as workersUtils from './workers/utils'; import TaskManager from './tasks/TaskManager'; import { serverManifest as clientServerManifest } from './client/handlers'; -import { serverManifest as agentServerManifest } from './agent/handlers'; +import agentServerManifest from './nodes/agent/handlers'; -type NetworkConfig = { - // Agent QUICSocket config - agentHost?: string; - agentPort?: number; - ipv6Only?: boolean; - agentKeepAliveIntervalTime?: number; - agentMaxIdleTimeout?: number; - // RPCServer for client service - clientHost?: string; - clientPort?: number; - // Websocket server config - maxIdleTimeout?: number; - pingIntervalTime?: number; - pingTimeoutTimeTime?: number; - // RPC config - clientParserBufferByteLimit?: number; - handlerTimeoutTime?: number; - handlerTimeoutGraceTime?: number; +/** + * Optional configuration for `PolykeyAgent`. + */ +type PolykeyAgentOptions = { + nodePath: string; + clientServiceHost: string; + clientServicePort: number; + agentServiceHost: string; + agentServicePort: number; + seedNodes: SeedNodes; + workers: number; + ipv6Only: boolean; + keys: KeysOptions; + rpc: { + callTimeoutTime: number; + parserBufferSize: number; + }; + client: { + connectTimeoutTime: number; + keepAliveTimeoutTime: number; + keepAliveIntervalTime: number; + }; + nodes: NodesOptions; +}; + +type PolykeyAgentStartOptions = { + clientServiceHost: string; + clientServicePort: number; + agentServiceHost: string; + agentServicePort: number; + ipv6Only: boolean; + workers: number; }; interface PolykeyAgent extends CreateDestroyStartStop {} @CreateDestroyStartStop( new errors.ErrorPolykeyAgentRunning(), new errors.ErrorPolykeyAgentDestroyed(), + { + eventStart: events.EventPolykeyAgentStart, + eventStarted: events.EventPolykeyAgentStarted, + eventStop: events.EventPolykeyAgentStop, + eventStopped: events.EventPolykeyAgentStopped, + eventDestroy: events.EventPolykeyAgentDestroy, + eventDestroyed: events.EventPolykeyAgentDestroyed, + }, ) class PolykeyAgent { /** - * Event symbols - * These represent event topics + * Create the Polykey Agent. + * + * All optional configuration is deep-merged with defaults. + * + * If any of the optional dependencies is injected, their lifecycle will not + * be managed by `PolykeyAgent`. Furthermore if you inject an optional + * dependency, make sure you are injecting all upstream transitive + * dependencies at the same time. For example if you inject `acl`, you must + * also inject `db`. */ - public static readonly eventSymbols = { - [CertManager.name]: Symbol(CertManager.name), - [QUICServer.name]: Symbol(QUICServer.name), - } as { - readonly CertManager: unique symbol; - readonly QUICServer: unique symbol; - }; - public static async createPolykeyAgent({ // Required parameters password, - // Optional configuration - nodePath = config.defaults.nodePath, - keyRingConfig = {}, - certManagerConfig = {}, - networkConfig = {}, - nodeConnectionManagerConfig = {}, - seedNodes = {}, - workers, + // Options + options = {}, + fresh = false, // Optional dependencies - status, - schema, - keyRing, - db, - certManager, - identitiesManager, - sigchain, - acl, - gestaltGraph, - taskManager, - nodeGraph, - nodeConnectionManager, - nodeManager, - discovery, - vaultManager, - notificationsManager, - sessionManager, - rpcServerClient, - webSocketServerClient, - rpcServerAgent, - quicSocket, fs = require('fs'), logger = new Logger(this.name), - fresh = false, }: { password: string; - nodePath?: string; - keyRingConfig?: { - recoveryCode?: RecoveryCode; - privateKey?: PrivateKey; - privateKeyPath?: string; - passwordOpsLimit?: PasswordOpsLimit; - passwordMemLimit?: PasswordMemLimit; - strictMemoryLock?: boolean; - }; - certManagerConfig?: { - certDuration?: number; - }; - nodeConnectionManagerConfig?: { - connectionConnectTime?: number; - connectionTimeoutTime?: number; - initialClosestNodes?: number; - pingTimeoutTime?: number; - connectionHolePunchTimeoutTime?: number; - connectionHolePunchIntervalTime?: number; - }; - networkConfig?: NetworkConfig; - seedNodes?: SeedNodes; - workers?: number; - status?: Status; - schema?: Schema; - keyRing?: KeyRing; - db?: DB; - certManager?: CertManager; - identitiesManager?: IdentitiesManager; - sigchain?: Sigchain; - acl?: ACL; - gestaltGraph?: GestaltGraph; - taskManager?: TaskManager; - nodeGraph?: NodeGraph; - nodeConnectionManager?: NodeConnectionManager; - nodeManager?: NodeManager; - discovery?: Discovery; - vaultManager?: VaultManager; - notificationsManager?: NotificationsManager; - sessionManager?: SessionManager; - rpcServerClient?: RPCServer; - webSocketServerClient?: WebSocketServer; - rpcServerAgent?: RPCServer; - quicSocket?: QUICSocket; + options?: DeepPartial; fs?: FileSystem; logger?: Logger; fresh?: boolean; @@ -174,252 +120,236 @@ class PolykeyAgent { const umask = 0o077; logger.info(`Setting umask to ${umask.toString(8).padStart(3, '0')}`); process.umask(umask); - if (nodePath == null) { + const optionsDefaulted = utils.mergeObjects(options, { + nodePath: config.defaultsUser.nodePath, + clientServiceHost: config.defaultsUser.clientServiceHost, + clientServicePort: config.defaultsUser.clientServicePort, + agentServiceHost: config.defaultsUser.agentServiceHost, + agentServicePort: config.defaultsUser.agentServicePort, + seedNodes: config.defaultsUser.seedNodes, + workers: config.defaultsUser.workers, + ipv6Only: config.defaultsUser.ipv6Only, + keys: { + certDuration: config.defaultsUser.certDuration, + certRenewLeadTime: config.defaultsUser.certRenewLeadTime, + }, + rpc: { + callTimeoutTime: config.defaultsSystem.rpcCallTimeoutTime, + parserBufferSize: config.defaultsSystem.rpcParserBufferSize, + }, + client: { + connectTimoutTime: config.defaultsSystem.clientConnectTimeoutTime, + keepAliveTimeoutTime: config.defaultsSystem.clientKeepAliveTimeoutTime, + keepAliveIntervalTime: + config.defaultsSystem.clientKeepAliveIntervalTime, + }, + nodes: { + connectionIdleTimeoutTime: + config.defaultsSystem.nodesConnectionIdleTimeoutTime, + connectionFindConcurrencyLimit: + config.defaultsSystem.nodesConnectionFindConcurrencyLimit, + connectionConnectTimeoutTime: + config.defaultsSystem.nodesConnectionConnectTimeoutTime, + connectionKeepAliveTimeoutTime: + config.defaultsSystem.nodesConnectionKeepAliveTimeoutTime, + connectionKeepAliveIntervalTime: + config.defaultsSystem.nodesConnectionKeepAliveIntervalTime, + connectionHolePunchIntervalTime: + config.defaultsSystem.nodesConnectionHolePunchIntervalTime, + }, + }); + // This can only happen if the caller didn't specify the node path and the + // automatic detection failed + if (optionsDefaulted.nodePath == null) { throw new errors.ErrorUtilsNodePath(); } - logger.info(`Setting node path to ${nodePath}`); - const certManagerConfig_ = { - ...config.defaults.certManagerConfig, - ...utils.filterEmptyObject(certManagerConfig), - }; - const nodeConnectionManagerConfig_ = { - ...config.defaults.nodeConnectionManagerConfig, - ...utils.filterEmptyObject(nodeConnectionManagerConfig), - }; - const networkConfig_ = { - ...config.defaults.networkConfig, - ...utils.filterEmptyObject(networkConfig), - }; - - await utils.mkdirExists(fs, nodePath); - const statusPath = path.join(nodePath, config.defaults.statusBase); - const statusLockPath = path.join(nodePath, config.defaults.statusLockBase); - const statePath = path.join(nodePath, config.defaults.stateBase); - const dbPath = path.join(statePath, config.defaults.dbBase); - const keysPath = path.join(statePath, config.defaults.keysBase); - const vaultsPath = path.join(statePath, config.defaults.vaultsBase); - const events = new EventBus({ - captureRejections: true, - }); + logger.info(`Setting node path to ${optionsDefaulted.nodePath}`); + await utils.mkdirExists(fs, optionsDefaulted.nodePath); + const statusPath = path.join( + optionsDefaulted.nodePath, + config.paths.statusBase, + ); + const statusLockPath = path.join( + optionsDefaulted.nodePath, + config.paths.statusLockBase, + ); + const statePath = path.join( + optionsDefaulted.nodePath, + config.paths.stateBase, + ); + const dbPath = path.join(statePath, config.paths.dbBase); + const keysPath = path.join(statePath, config.paths.keysBase); + const vaultsPath = path.join(statePath, config.paths.vaultsBase); let pkAgentProm: PromiseDeconstructed | undefined; + + let status: Status | undefined; + let schema: Schema | undefined; + let keyRing: KeyRing | undefined; + let db: DB | undefined; + let taskManager: TaskManager | undefined; + let certManager: CertManager | undefined; + let sigchain: Sigchain | undefined; + let acl: ACL | undefined; + let gestaltGraph: GestaltGraph | undefined; + let identitiesManager: IdentitiesManager | undefined; + let nodeGraph: NodeGraph | undefined; + let nodeConnectionManager: NodeConnectionManager | undefined; + let nodeManager: NodeManager | undefined; + let discovery: Discovery | undefined; + let notificationsManager: NotificationsManager | undefined; + let vaultManager: VaultManager | undefined; + let sessionManager: SessionManager | undefined; + let rpcServerClient: RPCServer | undefined; + let webSocketServerClient: WebSocketServer | undefined; + let rpcServerAgent: RPCServer | undefined; try { - status = - status ?? - new Status({ - statusPath, - statusLockPath, - fs: fs, - logger: logger.getChild(Status.name), - }); + status = new Status({ + statusPath, + statusLockPath, + fs: fs, + logger: logger.getChild(Status.name), + }); // Start locking the status await status.start({ pid: process.pid }); - schema = - schema ?? - (await Schema.createSchema({ - statePath, - fs, - logger: logger.getChild(Schema.name), - fresh, - })); - keyRing = - keyRing ?? - (await KeyRing.createKeyRing({ - fresh, - fs, - keysPath, - password, - ...keyRingConfig, - logger: logger.getChild(KeyRing.name), - })); - // Remove your own node ID if provided as a seed node - const nodeIdOwn = keyRing.getNodeId(); - const nodeIdEncodedOwn = Object.keys(seedNodes).find((nodeIdEncoded) => { - return nodeIdOwn.equals(nodesUtils.decodeNodeId(nodeIdEncoded)!); + schema = await Schema.createSchema({ + statePath, + fs, + logger: logger.getChild(Schema.name), + fresh, }); - if (nodeIdEncodedOwn != null) { - delete seedNodes[nodeIdEncodedOwn]; - } - db = - db ?? - (await DB.createDB({ - dbPath, - crypto: { - key: keyRing.dbKey, - ops: { - encrypt: async (key, plainText) => { - return keysUtils.encryptWithKey( - utils.bufferWrap(key) as Key, - utils.bufferWrap(plainText), - ); - }, - decrypt: async (key, cipherText) => { - return keysUtils.decryptWithKey( - utils.bufferWrap(key) as Key, - utils.bufferWrap(cipherText), - ); - }, - }, - }, - fs, - logger: logger.getChild(DB.name), - fresh, - })); - taskManager = - taskManager ?? - (await TaskManager.createTaskManager({ - db, - fresh, - lazy: true, - logger, - })); - certManager = - certManager ?? - (await CertManager.createCertManager({ - keyRing, - db, - taskManager, - changeCallback: async (data) => - events.emitAsync(PolykeyAgent.eventSymbols.CertManager, data), - logger: logger.getChild(CertManager.name), - fresh, - ...certManagerConfig_, - })); - sigchain = - sigchain ?? - (await Sigchain.createSigchain({ - keyRing, - db, - logger: logger.getChild(Sigchain.name), - fresh, - })); - acl = - acl ?? - (await ACL.createACL({ - db, - logger: logger.getChild(ACL.name), - fresh, - })); - gestaltGraph = - gestaltGraph ?? - (await GestaltGraph.createGestaltGraph({ - db, - acl, - logger: logger.getChild(GestaltGraph.name), - fresh, - })); - identitiesManager = - identitiesManager ?? - (await IdentitiesManager.createIdentitiesManager({ - keyRing, - db, - sigchain, - gestaltGraph, - logger: logger.getChild(IdentitiesManager.name), - fresh, - })); - // Registering providers - const githubProvider = new providers.GithubProvider({ - clientId: config.providers['github.com'].clientId, - logger: logger.getChild(providers.GithubProvider.name), + keyRing = await KeyRing.createKeyRing({ + keysPath, + options: optionsDefaulted.keys, + fs, + fresh, + password, + logger: logger.getChild(KeyRing.name), }); - identitiesManager.registerProvider(githubProvider); - nodeGraph = - nodeGraph ?? - (await NodeGraph.createNodeGraph({ - db, - fresh, - keyRing, - logger: logger.getChild(NodeGraph.name), - })); - const resolveHostname = (host) => { - return networkUtils.resolveHostname(host)[0] ?? ''; - }; - quicSocket = - quicSocket ?? - new QUICSocket({ - logger: logger.getChild(QUICSocket.name), - resolveHostname, - }); - const crypto: ServerCrypto & ClientCrypto = { - randomBytes: async (data: ArrayBuffer) => { - const randomBytes = keysUtils.getRandomBytes(data.byteLength); - const dataBuf = Buffer.from(data); - dataBuf.write(randomBytes.toString('binary'), 'binary'); - }, - async sign(key: ArrayBuffer, data: ArrayBuffer) { - const cryptoKey = await webcrypto.subtle.importKey( - 'raw', - key, - { - name: 'HMAC', - hash: 'SHA-256', + db = await DB.createDB({ + dbPath, + crypto: { + key: keyRing.dbKey, + ops: { + encrypt: async (key, plainText) => { + return keysUtils.encryptWithKey( + utils.bufferWrap(key) as Key, + utils.bufferWrap(plainText), + ); }, - true, - ['sign', 'verify'], - ); - return webcrypto.subtle.sign('HMAC', cryptoKey, data); - }, - async verify(key: ArrayBuffer, data: ArrayBuffer, sig: ArrayBuffer) { - const cryptoKey = await webcrypto.subtle.importKey( - 'raw', - key, - { - name: 'HMAC', - hash: 'SHA-256', + decrypt: async (key, cipherText) => { + return keysUtils.decryptWithKey( + utils.bufferWrap(key) as Key, + utils.bufferWrap(cipherText), + ); }, - true, - ['sign', 'verify'], - ); - return webcrypto.subtle.verify('HMAC', cryptoKey, sig, data); + }, }, - }; + fs, + logger: logger.getChild(DB.name), + fresh, + }); + taskManager = await TaskManager.createTaskManager({ + db, + fresh, + lazy: true, + logger, + }); + certManager = await CertManager.createCertManager({ + db, + keyRing, + taskManager, + options: optionsDefaulted.keys, + logger: logger.getChild(CertManager.name), + fresh, + }); + // TLS configuration for networking const tlsConfig: TLSConfig = { keyPrivatePem: keysUtils.privateKeyToPEM(keyRing.keyPair.privateKey), certChainPem: await certManager.getCertPEMsChainPEM(), }; - nodeConnectionManager = - nodeConnectionManager ?? - new NodeConnectionManager({ - handleStream: () => {}, - keyRing, - nodeGraph, - seedNodes, - quicSocket, - ...nodeConnectionManagerConfig_, - connectionKeepAliveIntervalTime: - networkConfig_.agentKeepAliveIntervalTime, - connectionMaxIdleTimeout: networkConfig_.agentMaxIdleTimeout, - tlsConfig, - crypto, - logger: logger.getChild(NodeConnectionManager.name), - }); - nodeManager = - nodeManager ?? - new NodeManager({ - db, - sigchain, - keyRing, - nodeGraph, - nodeConnectionManager, - taskManager, - gestaltGraph, - logger: logger.getChild(NodeManager.name), - }); + sigchain = await Sigchain.createSigchain({ + db, + keyRing, + logger: logger.getChild(Sigchain.name), + fresh, + }); + acl = await ACL.createACL({ + db, + logger: logger.getChild(ACL.name), + fresh, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger: logger.getChild(GestaltGraph.name), + fresh, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + keyRing, + db, + sigchain, + gestaltGraph, + logger: logger.getChild(IdentitiesManager.name), + fresh, + }); + // Registering providers + const githubProvider = new providers.GithubProvider({ + clientId: config.providers['github.com'].clientId, + logger: logger.getChild(providers.GithubProvider.name), + }); + identitiesManager.registerProvider(githubProvider); + nodeGraph = await NodeGraph.createNodeGraph({ + db, + fresh, + keyRing, + logger: logger.getChild(NodeGraph.name), + }); + // Remove your own node ID if provided as a seed node + const nodeIdOwnEncoded = nodesUtils.encodeNodeId(keyRing.getNodeId()); + delete optionsDefaulted.seedNodes[nodeIdOwnEncoded]; + nodeConnectionManager = new NodeConnectionManager({ + keyRing, + nodeGraph, + tlsConfig, + seedNodes: optionsDefaulted.seedNodes, + options: optionsDefaulted.nodes, + logger: logger.getChild(NodeConnectionManager.name), + }); + nodeManager = new NodeManager({ + db, + sigchain, + keyRing, + nodeGraph, + nodeConnectionManager, + taskManager, + gestaltGraph, + logger: logger.getChild(NodeManager.name), + }); await nodeManager.start(); - discovery = - discovery ?? - (await Discovery.createDiscovery({ - db, - keyRing, - gestaltGraph, - identitiesManager, - nodeManager, - taskManager, - logger: logger.getChild(Discovery.name), - })); + // Add seed nodes to the nodeGraph + const setNodeProms = new Array>(); + for (const nodeIdEncoded in optionsDefaulted.seedNodes) { + const nodeId = nodesUtils.decodeNodeId(nodeIdEncoded); + if (nodeId == null) utils.never(); + const setNodeProm = nodeManager.setNode( + nodeId, + optionsDefaulted.seedNodes[nodeIdEncoded], + true, + ); + setNodeProms.push(setNodeProm); + } + await Promise.all(setNodeProms); + discovery = await Discovery.createDiscovery({ + db, + keyRing, + gestaltGraph, + identitiesManager, + nodeManager, + taskManager, + logger: logger.getChild(Discovery.name), + }); notificationsManager = - notificationsManager ?? - (await NotificationsManager.createNotificationsManager({ + await NotificationsManager.createNotificationsManager({ acl, db, nodeConnectionManager, @@ -427,104 +357,74 @@ class PolykeyAgent { keyRing, logger: logger.getChild(NotificationsManager.name), fresh, - })); - vaultManager = - vaultManager ?? - (await VaultManager.createVaultManager({ - vaultsPath, - keyRing, - nodeConnectionManager, - notificationsManager, - gestaltGraph, - acl, - db, - fs, - logger: logger.getChild(VaultManager.name), - fresh, - })); - sessionManager = - sessionManager ?? - (await SessionManager.createSessionManager({ - db, - keyRing, - logger: logger.getChild(SessionManager.name), - fresh, - })); - // If a recovery code is provided then we reset any sessions in case the - // password changed. - if (keyRingConfig.recoveryCode != null) await sessionManager.resetKey(); - if (rpcServerClient == null) { - pkAgentProm = utils.promise(); - rpcServerClient = await RPCServer.createRPCServer({ - manifest: clientServerManifest({ - acl: acl, - certManager: certManager, - db: db, - discovery: discovery, - fs: fs, - gestaltGraph: gestaltGraph, - identitiesManager: identitiesManager, - keyRing: keyRing, - logger: logger, - nodeConnectionManager: nodeConnectionManager, - nodeGraph: nodeGraph, - nodeManager: nodeManager, - notificationsManager: notificationsManager, - pkAgentProm: pkAgentProm.p, - sessionManager: sessionManager, - vaultManager: vaultManager, - }), - middlewareFactory: rpcUtilsMiddleware.defaultServerMiddlewareWrapper( - clientUtilsMiddleware.middlewareServer(sessionManager, keyRing), - networkConfig_.clientParserBufferByteLimit, - ), - sensitive: false, - handlerTimeoutTime: networkConfig_.handlerTimeoutTime, - handlerTimeoutGraceTime: networkConfig_.handlerTimeoutGraceTime, - logger: logger.getChild(RPCServer.name + 'Client'), - }); - } - webSocketServerClient = - webSocketServerClient ?? - (await WebSocketServer.createWebSocketServer({ - connectionCallback: (rpcStream) => - rpcServerClient!.handleStream(rpcStream), - host: networkConfig_.clientHost, - port: networkConfig_.clientPort, - tlsConfig, - maxIdleTimeout: networkConfig_.maxIdleTimeout, - pingIntervalTime: networkConfig_.pingIntervalTime, - pingTimeoutTimeTime: networkConfig_.pingTimeoutTimeTime, - logger: logger.getChild('WebSocketServer'), - })); - if (rpcServerAgent == null) { - rpcServerAgent = await RPCServer.createRPCServer({ - manifest: agentServerManifest({ - acl: acl, - db: db, - keyRing: keyRing, - logger: logger, - nodeConnectionManager: nodeConnectionManager, - nodeGraph: nodeGraph, - nodeManager: nodeManager, - notificationsManager: notificationsManager, - sigchain: sigchain, - vaultManager: vaultManager, - }), - middlewareFactory: rpcUtilsMiddleware.defaultServerMiddlewareWrapper( - undefined, - networkConfig_.clientParserBufferByteLimit, - ), - sensitive: true, - handlerTimeoutTime: networkConfig_.handlerTimeoutTime, - handlerTimeoutGraceTime: networkConfig_.handlerTimeoutGraceTime, - logger: logger.getChild(RPCServer.name + 'Agent'), }); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + keyRing, + nodeConnectionManager, + notificationsManager, + gestaltGraph, + acl, + db, + fs, + logger: logger.getChild(VaultManager.name), + fresh, + }); + sessionManager = await SessionManager.createSessionManager({ + db, + keyRing, + logger: logger.getChild(SessionManager.name), + fresh, + }); + // If a recovery code is provided then we reset any sessions in case the + // password changed. + if (optionsDefaulted.keys.recoveryCode != null) { + await sessionManager.resetKey(); } + pkAgentProm = utils.promise(); + rpcServerClient = await RPCServer.createRPCServer({ + manifest: clientServerManifest({ + acl: acl, + certManager: certManager, + db: db, + discovery: discovery, + fs: fs, + gestaltGraph: gestaltGraph, + identitiesManager: identitiesManager, + keyRing: keyRing, + logger: logger, + nodeConnectionManager: nodeConnectionManager, + nodeGraph: nodeGraph, + nodeManager: nodeManager, + notificationsManager: notificationsManager, + pkAgentProm: pkAgentProm.p, + sessionManager: sessionManager, + vaultManager: vaultManager, + }), + middlewareFactory: rpcUtilsMiddleware.defaultServerMiddlewareWrapper( + clientUtilsMiddleware.middlewareServer(sessionManager, keyRing), + optionsDefaulted.rpc.parserBufferSize, + ), + sensitive: false, + handlerTimeoutTime: optionsDefaulted.rpc.callTimeoutTime, + handlerTimeoutGraceTime: optionsDefaulted.rpc.callTimeoutTime + 2000, + logger: logger.getChild(RPCServer.name + 'Client'), + }); + webSocketServerClient = await WebSocketServer.createWebSocketServer({ + connectionCallback: (rpcStream) => + rpcServerClient!.handleStream(rpcStream), + host: optionsDefaulted.clientServiceHost, + port: optionsDefaulted.clientServicePort, + tlsConfig, + // FIXME: Not sure about this, maxIdleTimeout doesn't seem to be used? + maxIdleTimeout: optionsDefaulted.client.keepAliveTimeoutTime, + pingIntervalTime: optionsDefaulted.client.keepAliveIntervalTime, + pingTimeoutTimeTime: optionsDefaulted.client.keepAliveTimeoutTime, + logger: logger.getChild('WebSocketServer'), + }); } catch (e) { logger.warn(`Failed Creating ${this.name}`); - await quicSocket?.stop({ force: true }); - await rpcServerAgent?.destroy(true); + await rpcServerAgent?.destroy({ force: true }); await rpcServerClient?.destroy(); await webSocketServerClient?.stop(true); await sessionManager?.stop(); @@ -544,7 +444,7 @@ class PolykeyAgent { throw e; } const pkAgent = new this({ - nodePath, + nodePath: optionsDefaulted.nodePath, status, schema, keyRing, @@ -564,17 +464,21 @@ class PolykeyAgent { sessionManager, rpcServerClient, webSocketServerClient, - rpcServerAgent, - quicSocket, - events, fs, logger, }); pkAgentProm?.resolveP(pkAgent); + await pkAgent.start({ password, - networkConfig, - workers, + options: { + clientServiceHost: optionsDefaulted.clientServiceHost, + clientServicePort: optionsDefaulted.clientServicePort, + agentServiceHost: optionsDefaulted.agentServiceHost, + agentServicePort: optionsDefaulted.agentServicePort, + workers: optionsDefaulted.workers, + ipv6Only: optionsDefaulted.ipv6Only, + }, fresh, }); logger.info(`Created ${this.name}`); @@ -599,15 +503,33 @@ class PolykeyAgent { public readonly vaultManager: VaultManager; public readonly notificationsManager: NotificationsManager; public readonly sessionManager: SessionManager; - public readonly events: EventBus; public readonly fs: FileSystem; public readonly logger: Logger; public readonly rpcServerClient: RPCServer; public readonly webSocketServerClient: WebSocketServer; public readonly rpcServerAgent: RPCServer; - public readonly quicSocket: QUICSocket; protected workerManager: PolykeyWorkerManagerInterface | undefined; + protected handleEventCertManagerCertChange = async ( + evt: keysEvents.EventCertManagerCertChange, + ) => { + const data = evt.detail; + this.logger.info(`${KeyRing.name} change propagating`); + await this.status.updateStatusLive({ + nodeId: data.nodeId, + }); + await this.nodeManager.resetBuckets(); + // Update the sigchain + await this.sigchain.onKeyRingChange(); + const tlsConfig: TLSConfig = { + keyPrivatePem: keysUtils.privateKeyToPEM(data.keyPair.privateKey), + certChainPem: await this.certManager.getCertPEMsChainPEM(), + }; + this.webSocketServerClient.setTlsConfig(tlsConfig); + this.nodeConnectionManager.updateTlsConfig(tlsConfig); + this.logger.info(`${KeyRing.name} change propagated`); + }; + constructor({ nodePath, status, @@ -629,9 +551,6 @@ class PolykeyAgent { sessionManager, rpcServerClient, webSocketServerClient, - rpcServerAgent, - quicSocket, - events, fs, logger, }: { @@ -655,9 +574,6 @@ class PolykeyAgent { sessionManager: SessionManager; rpcServerClient: RPCServer; webSocketServerClient: WebSocketServer; - rpcServerAgent: RPCServer; - quicSocket: QUICSocket; - events: EventBus; fs: FileSystem; logger: Logger; }) { @@ -682,86 +598,37 @@ class PolykeyAgent { this.sessionManager = sessionManager; this.rpcServerClient = rpcServerClient; this.webSocketServerClient = webSocketServerClient; - this.rpcServerAgent = rpcServerAgent; - this.quicSocket = quicSocket; - this.events = events; this.fs = fs; } + // TODO: add getters for runtime service information? + public async start({ password, - networkConfig = {}, + options = {}, workers, fresh = false, }: { password: string; - networkConfig?: NetworkConfig; + options?: Partial; workers?: number; fresh?: boolean; }) { + const optionsDefaulted = utils.mergeObjects(options, { + clientServiceHost: config.defaultsUser.clientServiceHost, + clientServicePort: config.defaultsUser.clientServicePort, + agentServiceHost: config.defaultsUser.agentServiceHost, + agentServicePort: config.defaultsUser.agentServicePort, + workers: config.defaultsUser.workers, + ipv6Only: config.defaultsUser.ipv6Only, + }); try { this.logger.info(`Starting ${this.constructor.name}`); - // Set up error handling for event handlers - this.events[captureRejectionSymbol] = (err, event: symbol) => { - let msg = `EventBus error for ${event.toString()}`; - if (err instanceof errors.ErrorPolykey) { - msg += `: ${err.name}: ${err.description}`; - if (err.message !== '') { - msg += ` - ${err.message}`; - } - } else { - msg += `: ${err.name}`; - if (err.message !== '') { - msg += `: ${err.message}`; - } - } - this.logger.error(msg); - throw err; - }; // Register event handlers - this.events.on( - PolykeyAgent.eventSymbols.CertManager, - async (data: CertManagerChangeData) => { - this.logger.info(`${KeyRing.name} change propagating`); - await this.status.updateStatusLive({ - nodeId: data.nodeId, - }); - await this.nodeManager.resetBuckets(); - // Update the sigchain - await this.sigchain.onKeyRingChange(); - const tlsConfig: TLSConfig = { - keyPrivatePem: keysUtils.privateKeyToPEM(data.keyPair.privateKey), - certChainPem: await this.certManager.getCertPEMsChainPEM(), - }; - this.webSocketServerClient.setTlsConfig(tlsConfig); - this.nodeConnectionManager.updateTlsConfig(tlsConfig); - this.logger.info(`${KeyRing.name} change propagated`); - }, - ); - this.events.on( - PolykeyAgent.eventSymbols.QUICServer, - async (data: ConnectionData) => { - if (this.keyRing.getNodeId().equals(data.remoteNodeId)) return; - const address = networkUtils.buildAddress( - data.remoteHost, - data.remotePort, - ); - const nodeIdEncoded = nodesUtils.encodeNodeId(data.remoteNodeId); - this.logger.info( - `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, - }); - }, + this.certManager.addEventListener( + keysEvents.EventCertManagerCertChange.name, + this.handleEventCertManagerCertChange, ); - const _networkConfig = { - ...config.defaults.networkConfig, - ...utils.filterEmptyObject(networkConfig), - }; await this.status.start({ pid: process.pid }); await this.schema.start({ fresh }); // Starting modules @@ -808,21 +675,28 @@ class PolykeyAgent { // Client server await this.webSocketServerClient.start({ tlsConfig, - host: _networkConfig.clientHost, - port: _networkConfig.clientPort, + host: optionsDefaulted.clientServiceHost, + port: optionsDefaulted.clientServicePort, connectionCallback: (streamPair) => this.rpcServerClient.handleStream(streamPair), }); - // Agent server - await this.quicSocket.start({ - host: _networkConfig.agentHost, - port: _networkConfig.agentPort, - ipv6Only: _networkConfig.ipv6Only, - }); await this.nodeManager.start(); await this.nodeConnectionManager.start({ - nodeManager: this.nodeManager, - handleStream: (stream) => this.rpcServerAgent.handleStream(stream), + host: optionsDefaulted.agentServiceHost, + port: optionsDefaulted.agentServicePort, + ipv6Only: optionsDefaulted.ipv6Only, + manifest: agentServerManifest({ + acl: this.acl, + db: this.db, + keyRing: this.keyRing, + logger: this.logger, + nodeConnectionManager: this.nodeConnectionManager, + nodeGraph: this.nodeGraph, + nodeManager: this.nodeManager, + notificationsManager: this.notificationsManager, + sigchain: this.sigchain, + vaultManager: this.vaultManager, + }), }); await this.nodeGraph.start({ fresh }); await this.nodeManager.syncNodeGraph(false); @@ -845,15 +719,18 @@ class PolykeyAgent { nodeId: this.keyRing.getNodeId(), clientHost: this.webSocketServerClient.getHost(), clientPort: this.webSocketServerClient.getPort(), - agentHost: this.quicSocket.host, - agentPort: this.quicSocket.port, + agentHost: this.nodeConnectionManager.host, + agentPort: this.nodeConnectionManager.port, }); this.logger.info(`Started ${this.constructor.name}`); } catch (e) { this.logger.warn( `Failed Starting ${this.constructor.name} with ${e.message}`, ); - this.events.removeAllListeners(); + this.certManager.removeEventListener( + keysEvents.EventCertManagerCertChange.name, + this.handleEventCertManagerCertChange, + ); await this.status?.beginStop({ pid: process.pid }); await this.taskManager?.stopProcessing(); await this.taskManager?.stopTasks(); @@ -864,7 +741,6 @@ class PolykeyAgent { await this.nodeGraph?.stop(); await this.nodeConnectionManager?.stop(); await this.nodeManager?.stop(); - await this.quicSocket.stop(); await this.webSocketServerClient.stop(true); await this.identitiesManager?.stop(); await this.gestaltGraph?.stop(); @@ -888,7 +764,10 @@ class PolykeyAgent { */ public async stop() { this.logger.info(`Stopping ${this.constructor.name}`); - this.events.removeAllListeners(); + this.certManager.removeEventListener( + keysEvents.EventCertManagerCertChange.name, + this.handleEventCertManagerCertChange, + ); await this.status.beginStop({ pid: process.pid }); await this.taskManager.stopProcessing(); await this.taskManager.stopTasks(); @@ -899,7 +778,6 @@ class PolykeyAgent { await this.nodeConnectionManager.stop(); await this.nodeGraph.stop(); await this.nodeManager.stop(); - await this.quicSocket.stop(); await this.webSocketServerClient.stop(true); await this.identitiesManager.stop(); await this.gestaltGraph.stop(); @@ -969,3 +847,5 @@ class PolykeyAgent { } export default PolykeyAgent; + +export type { PolykeyAgentOptions }; diff --git a/src/PolykeyClient.ts b/src/PolykeyClient.ts index 17b7bc046..24bfd00f1 100644 --- a/src/PolykeyClient.ts +++ b/src/PolykeyClient.ts @@ -7,8 +7,9 @@ import RPCClient from './rpc/RPCClient'; import * as rpcUtilsMiddleware from './rpc/utils/middleware'; import * as clientUtilsMiddleware from './client/utils/middleware'; import { Session } from './sessions'; -import * as errors from './errors'; import * as utils from './utils'; +import * as errors from './errors'; +import * as events from './events'; import config from './config'; import { clientManifest } from './client/handlers/clientManifest'; @@ -21,12 +22,18 @@ interface PolykeyClient extends CreateDestroyStartStop {} @CreateDestroyStartStop( new errors.ErrorPolykeyClientRunning(), new errors.ErrorPolykeyClientDestroyed(), + { + eventStart: events.EventPolykeyClientStart, + eventStarted: events.EventPolykeyClientStarted, + eventStop: events.EventPolykeyClientStop, + eventStopped: events.EventPolykeyClientStopped, + eventDestroy: events.EventPolykeyClientDestroy, + eventDestroyed: events.EventPolykeyClientDestroyed, + }, ) class PolykeyClient { static async createPolykeyClient({ - nodePath = config.defaults.nodePath, - session, - rpcClientClient, + nodePath = config.defaultsUser.nodePath, streamFactory, streamKeepAliveTimeoutTime, parserBufferByteLimit, @@ -35,8 +42,6 @@ class PolykeyClient { fresh = false, }: { nodePath?: string; - session?: Session; - rpcClientClient?: RPCClient; streamFactory: StreamFactory; streamKeepAliveTimeoutTime?: number; parserBufferByteLimit?: number; @@ -49,29 +54,25 @@ class PolykeyClient { throw new errors.ErrorUtilsNodePath(); } await utils.mkdirExists(fs, nodePath); - const sessionTokenPath = path.join(nodePath, config.defaults.tokenBase); - session = - session ?? - (await Session.createSession({ - sessionTokenPath, - logger: logger.getChild(Session.name), - fresh, - })); - const rpcClientClient_ = - rpcClientClient ?? - (await RPCClient.createRPCClient({ - manifest: clientManifest, - streamFactory, - middlewareFactory: rpcUtilsMiddleware.defaultClientMiddlewareWrapper( - clientUtilsMiddleware.middlewareClient(session), - parserBufferByteLimit, - ), - streamKeepAliveTimeoutTime, - logger: logger.getChild(RPCClient.name), - })); + const sessionTokenPath = path.join(nodePath, config.paths.tokenBase); + const session = await Session.createSession({ + sessionTokenPath, + logger: logger.getChild(Session.name), + fresh, + }); + const rpcClientClient = await RPCClient.createRPCClient({ + manifest: clientManifest, + streamFactory, + middlewareFactory: rpcUtilsMiddleware.defaultClientMiddlewareWrapper( + clientUtilsMiddleware.middlewareClient(session), + parserBufferByteLimit, + ), + streamKeepAliveTimeoutTime, + logger: logger.getChild(RPCClient.name), + }); const pkClient = new this({ nodePath, - rpcClientClient: rpcClientClient_, + rpcClientClient: rpcClientClient, session, fs, logger, diff --git a/src/acl/ACL.ts b/src/acl/ACL.ts index 2270d0cee..beb4fc4fa 100644 --- a/src/acl/ACL.ts +++ b/src/acl/ACL.ts @@ -17,11 +17,20 @@ import { } from '@matrixai/async-init/dist/CreateDestroyStartStop'; import * as aclUtils from './utils'; import * as aclErrors from './errors'; +import * as events from './events'; interface ACL extends CreateDestroyStartStop {} @CreateDestroyStartStop( new aclErrors.ErrorACLRunning(), new aclErrors.ErrorACLDestroyed(), + { + eventStart: events.EventACLStart, + eventStarted: events.EventACLStarted, + eventStop: events.EventACLStop, + eventStopped: events.EventACLStopped, + eventDestroy: events.EventACLDestroy, + eventDestroyed: events.EventACLDestroyed, + }, ) class ACL { static async createACL({ diff --git a/src/acl/errors.ts b/src/acl/errors.ts index 508513759..18d29cb40 100644 --- a/src/acl/errors.ts +++ b/src/acl/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorACL extends ErrorPolykey {} diff --git a/src/acl/events.ts b/src/acl/events.ts new file mode 100644 index 000000000..3282b4a1c --- /dev/null +++ b/src/acl/events.ts @@ -0,0 +1,25 @@ +import EventPolykey from '../EventPolykey'; + +abstract class EventACL extends EventPolykey {} + +class EventACLStart extends EventACL {} + +class EventACLStarted extends EventACL {} + +class EventACLStop extends EventACL {} + +class EventACLStopped extends EventACL {} + +class EventACLDestroy extends EventACL {} + +class EventACLDestroyed extends EventACL {} + +export { + EventACL, + EventACLStart, + EventACLStarted, + EventACLStop, + EventACLStopped, + EventACLDestroy, + EventACLDestroyed, +}; diff --git a/src/acl/index.ts b/src/acl/index.ts index aac6db388..adbff6e07 100644 --- a/src/acl/index.ts +++ b/src/acl/index.ts @@ -2,3 +2,4 @@ export { default as ACL } from './ACL'; export * as types from './types'; export * as utils from './utils'; export * as errors from './errors'; +export * as events from './events'; diff --git a/src/agent/handlers/clientManifest.ts b/src/agent/handlers/clientManifest.ts deleted file mode 100644 index f94bb5b73..000000000 --- a/src/agent/handlers/clientManifest.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type { AgentRPCRequestParams, AgentRPCResponseResult } from '../types'; -import type { - AgentClaimMessage, - ClaimIdMessage, - HolePunchRelayMessage, - NodeAddressMessage, - NodeIdMessage, - SignedNotificationEncoded, - VaultsScanMessage, -} from './types'; -import { - DuplexCaller, - RawCaller, - ServerCaller, - UnaryCaller, -} from '../../rpc/callers'; - -const nodesClaimsGet = new ServerCaller< - AgentRPCRequestParams, - AgentRPCResponseResult ->(); - -const nodesClosestLocalNodesGet = new ServerCaller< - AgentRPCRequestParams, - AgentRPCResponseResult ->(); - -const nodesCrossSignClaim = new DuplexCaller< - AgentRPCRequestParams, - AgentRPCResponseResult ->(); - -const nodesHolePunchMessageSend = new UnaryCaller< - AgentRPCRequestParams, - AgentRPCResponseResult ->(); - -const notificationsSend = new UnaryCaller< - AgentRPCRequestParams, - AgentRPCResponseResult ->(); - -const vaultsGitInfoGet = new RawCaller(); - -const vaultsGitPackGet = new RawCaller(); - -const vaultsScan = new ServerCaller< - AgentRPCRequestParams, - AgentRPCResponseResult ->(); - -/** - * All the client caller definitions for the AgentClient RPC. - * Used by the RPCClient to register callers and enforce types. - * - * No type used here, it will override type inference. - */ -const clientManifest = { - nodesClaimsGet, - nodesClosestLocalNodesGet, - nodesCrossSignClaim, - nodesHolePunchMessageSend, - notificationsSend, - vaultsGitInfoGet, - vaultsGitPackGet, - vaultsScan, -}; - -export { - clientManifest, - nodesClaimsGet, - nodesClosestLocalNodesGet, - nodesCrossSignClaim, - nodesHolePunchMessageSend, - notificationsSend, - vaultsGitInfoGet, - vaultsGitPackGet, - vaultsScan, -}; diff --git a/src/agent/handlers/index.ts b/src/agent/handlers/index.ts deleted file mode 100644 index 4fc2abce9..000000000 --- a/src/agent/handlers/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from './clientManifest'; -export * from './serverManifest'; -export * from './types'; -export * from './nodesClosestLocalNodesGet'; -export * from './nodesHolePunchMessageSend'; -export * from './nodesCrossSignClaim'; -export * from './notificationsSend'; -export * from './nodesClaimsGet'; -export * from './vaultsScan'; -export * from './vaultsGitInfoGet'; -export * from './vaultsGitPackGet'; diff --git a/src/agent/handlers/serverManifest.ts b/src/agent/handlers/serverManifest.ts deleted file mode 100644 index 952f66e24..000000000 --- a/src/agent/handlers/serverManifest.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type Logger from '@matrixai/logger'; -import type KeyRing from '../../keys/KeyRing'; -import type Sigchain from '../../sigchain/Sigchain'; -import type ACL from '../../acl/ACL'; -import type NodeGraph from '../../nodes/NodeGraph'; -import type NodeManager from '../../nodes/NodeManager'; -import type NodeConnectionManager from '../../nodes/NodeConnectionManager'; -import type { NotificationsManager } from '../../notifications'; -import type { VaultManager } from '../../vaults'; -import { NodesClosestLocalNodesGetHandler } from './nodesClosestLocalNodesGet'; -import { NodesHolePunchMessageSendHandler } from './nodesHolePunchMessageSend'; -import { NodesCrossSignClaimHandler } from './nodesCrossSignClaim'; -import { NotificationsSendHandler } from './notificationsSend'; -import { NodesClaimsGetHandler } from './nodesClaimsGet'; -import { VaultsScanHandler } from './vaultsScan'; -import { VaultsGitInfoGetHandler } from './vaultsGitInfoGet'; -import { VaultsGitPackGetHandler } from './vaultsGitPackGet'; - -/** - * All the server handler definitions for the AgentServer RPC. - * This will take the container of all the required dependencies and create the server handlers. - * - * Used by the RPCServer to register handlers and enforce types. - */ -const serverManifest = (container: { - db: DB; - sigchain: Sigchain; - nodeGraph: NodeGraph; - acl: ACL; - nodeManager: NodeManager; - nodeConnectionManager: NodeConnectionManager; - keyRing: KeyRing; - logger: Logger; - notificationsManager: NotificationsManager; - vaultManager: VaultManager; -}) => { - return { - nodesClaimsGet: new NodesClaimsGetHandler(container), - nodesClosestLocalNodesGet: new NodesClosestLocalNodesGetHandler(container), - nodesCrossSignClaim: new NodesCrossSignClaimHandler(container), - nodesHolePunchMessageSend: new NodesHolePunchMessageSendHandler(container), - notificationsSend: new NotificationsSendHandler(container), - vaultsGitInfoGet: new VaultsGitInfoGetHandler(container), - vaultsGitPackGet: new VaultsGitPackGetHandler(container), - vaultsScan: new VaultsScanHandler(container), - }; -}; - -export { serverManifest }; diff --git a/src/agent/handlers/types.ts b/src/agent/handlers/types.ts deleted file mode 100644 index 338997830..000000000 --- a/src/agent/handlers/types.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { SignedTokenEncoded } from '../../tokens/types'; -import type { ClaimIdEncoded, NodeIdEncoded, VaultIdEncoded } from '../../ids'; -import type { VaultAction, VaultName } from '../../vaults/types'; -import type { SignedNotification } from '../../notifications/types'; - -export type ClaimIdMessage = { - claimIdEncoded: ClaimIdEncoded; -}; - -export type AgentClaimMessage = Partial & { - signedTokenEncoded: SignedTokenEncoded; -}; - -export type NodeIdMessage = { - nodeIdEncoded: NodeIdEncoded; -}; - -export type AddressMessage = { - host: string; - port: number; -}; - -export type NodeAddressMessage = NodeIdMessage & AddressMessage; - -export type HolePunchRelayMessage = { - srcIdEncoded: NodeIdEncoded; - dstIdEncoded: NodeIdEncoded; - address?: AddressMessage; -}; - -export type SignedNotificationEncoded = { - signedNotificationEncoded: SignedNotification; -}; - -export type VaultInfo = { - vaultIdEncoded: VaultIdEncoded; - vaultName: VaultName; -}; - -export type VaultsScanMessage = VaultInfo & { - vaultPermissions: Array; -}; diff --git a/src/agent/index.ts b/src/agent/index.ts deleted file mode 100644 index 042db9ce9..000000000 --- a/src/agent/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './handlers'; -export * as types from './types'; -export * as utils from './utils'; diff --git a/src/agent/types.ts b/src/agent/types.ts deleted file mode 100644 index 88ebf3607..000000000 --- a/src/agent/types.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { JSONValue, ObjectEmpty } from '../types'; - -// Prevent overwriting the metadata type with `Omit<>` -type AgentRPCRequestParams = ObjectEmpty> = - { - metadata?: { - [Key: string]: JSONValue; - } & Partial<{ - authorization: string; - timeout: number; - }>; - } & Omit; - -// Prevent overwriting the metadata type with `Omit<>` -type AgentRPCResponseResult = ObjectEmpty> = - { - metadata?: { - [Key: string]: JSONValue; - } & Partial<{ - authorization: string; - timeout: number; - }>; - } & Omit; - -export type { AgentRPCRequestParams, AgentRPCResponseResult }; diff --git a/src/agent/utils.ts b/src/agent/utils.ts deleted file mode 100644 index d3ec8e103..000000000 --- a/src/agent/utils.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { NodeId } from '../ids/types'; -import * as keysUtils from '../keys/utils'; - -/** - * Used to extract the NodeId from the connection metadata. - * Used by the RPC handlers when they need to know the NodeId of the requester. - * @param meta - */ -function nodeIdFromMeta(meta: any): NodeId | undefined { - const remoteCerts = meta.remoteCertificates; - if (remoteCerts == null) return; - const leafCertPEM = remoteCerts[0]; - if (leafCertPEM == null) return; - const leafCert = keysUtils.certFromPEM(leafCertPEM); - if (leafCert == null) return; - const nodeId = keysUtils.certNodeId(leafCert); - if (nodeId == null) return; - return nodeId; -} - -export { nodeIdFromMeta }; diff --git a/src/bootstrap/errors.ts b/src/bootstrap/errors.ts index c2e25289c..ca0c38594 100644 --- a/src/bootstrap/errors.ts +++ b/src/bootstrap/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorBootstrap extends ErrorPolykey {} diff --git a/src/bootstrap/utils.ts b/src/bootstrap/utils.ts index b6659c493..2dc9f1243 100644 --- a/src/bootstrap/utils.ts +++ b/src/bootstrap/utils.ts @@ -1,48 +1,48 @@ -import type { FileSystem } from '../types'; -import type { RecoveryCode, Key, PrivateKey } from '../keys/types'; -import type { PasswordMemLimit, PasswordOpsLimit } from '../keys/types'; +import type { DeepPartial, FileSystem } from '../types'; +import type { RecoveryCode, Key, KeysOptions } from '../keys/types'; import path from 'path'; import Logger from '@matrixai/logger'; import { DB } from '@matrixai/db'; import * as bootstrapErrors from './errors'; import TaskManager from '../tasks/TaskManager'; -import { IdentitiesManager } from '../identities'; -import { SessionManager } from '../sessions'; -import { Status } from '../status'; -import { Schema } from '../schema'; -import { Sigchain } from '../sigchain'; -import { ACL } from '../acl'; -import { GestaltGraph } from '../gestalts'; -import { KeyRing } from '../keys'; -import { NodeGraph, NodeManager } from '../nodes'; -import { VaultManager } from '../vaults'; -import { NotificationsManager } from '../notifications'; -import { mkdirExists } from '../utils'; +import IdentitiesManager from '../identities/IdentitiesManager'; +import SessionManager from '../sessions/SessionManager'; +import Status from '../status/Status'; +import Schema from '../schema/Schema'; +import Sigchain from '../sigchain/Sigchain'; +import ACL from '../acl/ACL'; +import GestaltGraph from '../gestalts/GestaltGraph'; +import KeyRing from '../keys/KeyRing'; +import CertManager from '../keys/CertManager'; +import * as keysUtils from '../keys/utils'; +import NodeGraph from '../nodes/NodeGraph'; +import NodeManager from '../nodes/NodeManager'; +import VaultManager from '../vaults/VaultManager'; +import NotificationsManager from '../notifications/NotificationsManager'; import config from '../config'; import * as utils from '../utils'; -import * as keysUtils from '../keys/utils'; import * as errors from '../errors'; +type BootstrapOptions = { + nodePath: string; + keys: KeysOptions; +}; + /** * Bootstraps the Node Path */ async function bootstrapState({ + // Required parameters password, - nodePath = config.defaults.nodePath, - keyRingConfig = {}, + // Optional configuration + options = {}, fresh = false, + // Optional dependencies fs = require('fs'), logger = new Logger(bootstrapState.name), }: { password: string; - nodePath?: string; - keyRingConfig?: { - recoveryCode?: RecoveryCode; - privateKey?: PrivateKey; - privateKeyPath?: string; - passwordOpsLimit?: PasswordOpsLimit; - passwordMemLimit?: PasswordMemLimit; - }; + options?: DeepPartial; fresh?: boolean; fs?: FileSystem; logger?: Logger; @@ -50,18 +50,33 @@ async function bootstrapState({ const umask = 0o077; logger.info(`Setting umask to ${umask.toString(8).padStart(3, '0')}`); process.umask(umask); - logger.info(`Setting node path to ${nodePath}`); - if (nodePath == null) { + const optionsDefaulted = utils.mergeObjects(options, { + nodePath: config.defaultsUser.nodePath, + keys: { + certDuration: config.defaultsUser.certDuration, + }, + }); + logger.info(`Setting node path to ${optionsDefaulted.nodePath}`); + if (optionsDefaulted.nodePath == null) { throw new errors.ErrorUtilsNodePath(); } - await mkdirExists(fs, nodePath); + await utils.mkdirExists(fs, optionsDefaulted.nodePath); // Setup node path and sub paths - const statusPath = path.join(nodePath, config.defaults.statusBase); - const statusLockPath = path.join(nodePath, config.defaults.statusLockBase); - const statePath = path.join(nodePath, config.defaults.stateBase); - const dbPath = path.join(statePath, config.defaults.dbBase); - const keysPath = path.join(statePath, config.defaults.keysBase); - const vaultsPath = path.join(statePath, config.defaults.vaultsBase); + const statusPath = path.join( + optionsDefaulted.nodePath, + config.paths.statusBase, + ); + const statusLockPath = path.join( + optionsDefaulted.nodePath, + config.paths.statusLockBase, + ); + const statePath = path.join( + optionsDefaulted.nodePath, + config.paths.stateBase, + ); + const dbPath = path.join(statePath, config.paths.dbBase); + const keysPath = path.join(statePath, config.paths.keysBase); + const vaultsPath = path.join(statePath, config.paths.vaultsBase); const status = new Status({ statusPath, statusLockPath, @@ -72,7 +87,7 @@ async function bootstrapState({ await status.start({ pid: process.pid }); if (!fresh) { // Check the if number of directory entries is greater than 1 due to status.json and status.lock - if ((await fs.promises.readdir(nodePath)).length > 2) { + if ((await fs.promises.readdir(optionsDefaulted.nodePath)).length > 2) { throw new bootstrapErrors.ErrorBootstrapExistingState(); } } @@ -89,10 +104,10 @@ async function bootstrapState({ const keyRing = await KeyRing.createKeyRing({ keysPath, password, + options: optionsDefaulted.keys, fs, logger: logger.getChild(KeyRing.name), fresh, - ...keyRingConfig, }); const db = await DB.createDB({ dbPath, @@ -117,6 +132,19 @@ async function bootstrapState({ }, fresh, }); + const taskManager = await TaskManager.createTaskManager({ + db, + logger, + lazy: true, + }); + const certManager = await CertManager.createCertManager({ + keyRing, + db, + taskManager, + options: optionsDefaulted.keys, + fresh, + logger, + }); const sigchain = await Sigchain.createSigchain({ db, keyRing, @@ -148,11 +176,7 @@ async function bootstrapState({ keyRing, logger: logger.getChild(NodeGraph.name), }); - const taskManager = await TaskManager.createTaskManager({ - db, - logger, - lazy: true, - }); + const nodeManager = new NodeManager({ db, keyRing, @@ -199,6 +223,7 @@ async function bootstrapState({ await gestaltGraph.stop(); await acl.stop(); await sigchain.stop(); + await certManager.stop(); await taskManager.stop(); await db.stop(); await keyRing.stop(); @@ -210,3 +235,5 @@ async function bootstrapState({ } export { bootstrapState }; + +export type { BootstrapOptions }; diff --git a/src/claims/errors.ts b/src/claims/errors.ts index 83f88c520..dffdc336b 100644 --- a/src/claims/errors.ts +++ b/src/claims/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorClaims extends ErrorPolykey {} diff --git a/src/client/errors.ts b/src/client/errors.ts index e9acc8027..166db598a 100644 --- a/src/client/errors.ts +++ b/src/client/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorClient extends ErrorPolykey {} diff --git a/src/client/handlers/agentStatus.ts b/src/client/handlers/agentStatus.ts index dd5fe25d2..25bdf2b6c 100644 --- a/src/client/handlers/agentStatus.ts +++ b/src/client/handlers/agentStatus.ts @@ -20,8 +20,8 @@ class AgentStatusHandler extends UnaryHandler< nodeIdEncoded: nodesUtils.encodeNodeId(pkAgent.keyRing.getNodeId()), clientHost: pkAgent.webSocketServerClient.getHost(), clientPort: pkAgent.webSocketServerClient.getPort(), - agentHost: pkAgent.quicSocket.host, - agentPort: pkAgent.quicSocket.port, + agentHost: pkAgent.nodeConnectionManager.host, + agentPort: pkAgent.nodeConnectionManager.port, publicKeyJwk: keysUtils.publicKeyToJWK(pkAgent.keyRing.keyPair.publicKey), certChainPEM: await pkAgent.certManager.getCertPEMsChainPEM(), }; diff --git a/src/client/handlers/gestaltsGestaltList.ts b/src/client/handlers/gestaltsGestaltList.ts index 67ea5b8d3..69b1aa97e 100644 --- a/src/client/handlers/gestaltsGestaltList.ts +++ b/src/client/handlers/gestaltsGestaltList.ts @@ -20,9 +20,9 @@ class GestaltsGestaltListHandler extends ServerHandler< ctx, ): AsyncGenerator> { const { db, gestaltGraph } = this.container; - yield* db.withTransactionG(async function* ( - tran, - ): AsyncGenerator> { + yield* db.withTransactionG(async function* (tran): AsyncGenerator< + ClientRPCResponseResult + > { if (ctx.signal.aborted) throw ctx.signal.reason; for await (const gestalt of gestaltGraph.getGestalts(tran)) { const gestaltMessage: GestaltMessage = { diff --git a/src/config.ts b/src/config.ts index 4de7c276d..c92939f18 100644 --- a/src/config.ts +++ b/src/config.ts @@ -73,10 +73,9 @@ const config = { }, }, /** - * Default configuration + * File/directory paths */ - defaults: { - nodePath: getDefaultNodePath(), + paths: { statusBase: 'status.json', statusLockBase: 'status.lock', stateBase: 'state', @@ -86,73 +85,213 @@ const config = { vaultsBase: 'vaults', efsBase: 'efs', tokenBase: 'token', - certManagerConfig: { - certDuration: 31536000, - }, - networkConfig: { - /** - * Agent host defaults to `::` dual stack. - * This is because the agent service is supposed to be public. - */ - agentHost: '::', - agentPort: 0, - /** - * Client host defaults to `localhost`. - * This will depend on the OS configuration. - * Usually it will be IPv4 `127.0.0.1` or IPv6 `::1`. - * This is because the client service is private most of the time. - */ - clientHost: 'localhost', - clientPort: 0, - /** - * If using dual stack `::`, then this forces only IPv6 bindings. - */ - ipv6Only: false, - - /** - * Agent service transport keep alive interval time. - * This the maxmum time between keep alive messages. - * This only has effect if `agentMaxIdleTimeout` is greater than 0. - * See the transport layer for further details. - */ - agentKeepAliveIntervalTime: 10_000, // 10 seconds - - /** - * Agent service transport max idle timeout. - * This is the maximum time that a connection can be idle. - * This also controls how long the transport layer will dial - * for a client connection. - * See the transport layer for further details. - */ - agentMaxIdleTimeout: 60_000, // 1 minute - - clientMaxIdleTimeout: 120, // 2 minutes - clientPingIntervalTime: 1_000, // 1 second - clientPingTimeoutTimeTime: 10_000, // 10 seconds - - /** - * Controls the stream parser buffer limit. - * This is the maximum number of bytes that the stream parser - * will buffer before rejecting the RPC call. - */ - clientParserBufferByteLimit: 1_000_000, // About 1MB - clientHandlerTimeoutTime: 60_000, // 1 minute - clientHandlerTimeoutGraceTime: 2_000, // 2 seconds - }, - nodeConnectionManagerConfig: { - connectionConnectTime: 2000, - connectionTimeoutTime: 60000, - initialClosestNodes: 3, - pingTimeoutTime: 2000, - connectionHolePunchTimeoutTime: 4000, - connectionHolePunchIntervalTime: 250, - }, - // This is not used by the `PolykeyAgent` which defaults to `{}` - network: { - mainnet: mainnet, - testnet: testnet, - }, + }, + /** + * This is not used by the `PolykeyAgent` which defaults to `{}` + * In the future this will be replaced by `mainnet.polykey.com` and `testnet.polykey.com`. + * Along with the domain we will have the root public key too. + * + * Information that is pre-configured during distribution: + * + * - Domain + * - Root public key + * + * Information that is discovered over DNS (Authenticated DNS is optional): + * + * - IP address + * - Port + * + * As long as the root public key is provided, it is sufficient to defeat poisoning + * the network. The root public key should also be changed often to reduce the impact + * of compromises. Finally the root public key can also be signed by a third party CA + * providing an extra level of confidence. However this is not required. + */ + network: { + mainnet: mainnet, + testnet: testnet, + }, + /** + * Default system configuration. + * These are not meant to be changed by the user. + * These constants are tuned for optimal operation by the developers. + */ + defaultsSystem: { + /** + * Timeout for each RPC stream. + * + * The semantics of this timeout changes depending on the context of how it + * is used. + * + * It is reset upon sending or receiving any data on the stream. This is a + * one-shot timer on unary calls. This repeats for every chunk of data on + * streaming calls. + * + * This is the default for both client calls and server handlers. Both the + * client callers and server handlers can optionally override this default. + * + * When the server handler receives a desired timeout from the client call, + * the server handler will always choose the minimum of the timeouts between + * the client call and server handler. + * + * With respect to client calls, this timeout bounds the time that the client + * will wait for responses from the server, as well as the time to wait for + * additional to be sent to the server. + * + * With respect to server handlers, this timeout bounds the time that the + * server waits to send data back to the client, as well as the time to wait + * for additional client data. + * + * Therefore it is expected that specific clients calls and server handlers + * will override this timeout to cater to their specific circumstances. + */ + rpcCallTimeoutTime: 15_000, // 15 seconds + /** + * Buffer size of the JSON RPC parser. + * + * This limits the largest parseable JSON message. Any JSON RPC message + * greater than this byte size will be rejecte by closing the RPC stream + * with an error. + * + * This has no effect on raw streams as raw streams do not use any parser. + */ + rpcParserBufferSize: 64 * 1024, // 64 KiB + /** + * Timeout for the transport connecting to the client service. + * + * This bounds the amount of time that the client transport will wait to + * establish a connection to the client service of a Polykey Agent. + */ + clientConnectTimeoutTime: 15_000, // 15 seconds + /** + * Timeout for the keep alive of the transport connection to the client + * service. + * + * It is reset upon sending or receiving any data on the client service + * transport connection. + * + * This is the default for both sides (client and server) of the connection. + * + * This should always be greater than the connect timeout. + */ + clientKeepAliveTimeoutTime: 30_000, // 30 seconds (3x of interval time) + /** + * Interval for the keep alive of the transport connection to the client + * service. + * + * This is the minimum interval time because transport optimisations may + * increase the effective interval time when a keep alive message is not + * necessary, possibly due to other data being sent or received on the + * connection. + */ + clientKeepAliveIntervalTime: 10_000, // 10 seconds + /** + * Concurrency pool limit when finding other nodes. + * + * This is the parallel constant in the kademlia algorithm. It controls + * how many parallel connections when attempting to find a node across + * the network. + */ + nodesConnectionFindConcurrencyLimit: 3, + /** + * Timeout for idle node connections. + * + * A node connection is idle, if nothing is using the connection. A + * connection is being used when its resource counter is above 0. + * + * The resource counter of node connections is incremented above 0 + * when a reference to the node connection is maintained, usually with + * the bracketing pattern. + * + * This has nothing to do with the data being sent or received on the + * connection. It's intended as a way of garbage collecting unused + * connections. + * + * This should always be greater than the keep alive timeout. + */ + nodesConnectionIdleTimeoutTime: 60_000, // 60 seconds + /** + * Timeout for establishing a node connection. + * + * This applies to both normal "forward" connections and "reverse" + * connections started by hole punching. Reverse connections + * is started by signalling requests that result in hole punching. + * + * This is the default for both client and server sides of the connection. + * + * Due to transport layer implementation peculiarities, this should never + * be greater than the keep alive timeout. + */ + nodesConnectionConnectTimeoutTime: 15_000, // 15 seconds + /** + * Timeout for the keep alive of the node connection. + * + * It is reset upon sending or receiving any data on the connection. + * + * This is the default for both sides (client and server) of the connection. + * + * This should always be greater than the connect timeout. + */ + nodesConnectionKeepAliveTimeoutTime: 30_000, // 30 seconds (3x of interval time) + /** + * Interval for the keep alive of the node connection. + * + * This is the minimum interval time because transport optimisations may + * increase the effective interval time when a keep alive message is not + * necessary, possibly due to other data being sent or received on the + * connection. + */ + nodesConnectionKeepAliveIntervalTime: 10_000, // 10 seconds + /** + * Interval for hole punching reverse node connections. + */ + nodesConnectionHolePunchIntervalTime: 1_000, // 1 second + }, + /** + * Default user configuration. + * These are meant to be changed by the user. + * However the defaults here provide the average user experience. + */ + defaultsUser: { + nodePath: getDefaultNodePath(), + certDuration: 31536000, + certRenewLeadTime: 86400, + /** + * Client host defaults to `localhost`. + * This will depend on the OS configuration. + * Usually it will be IPv4 `127.0.0.1` or IPv6 `::1`. + * This is because the client service is private most of the time. + */ + clientServiceHost: 'localhost', + clientServicePort: 0, + /** + * Agent host defaults to `::` dual stack. + * This is because the agent service is supposed to be public. + */ + agentServiceHost: '::', + agentServicePort: 0, + /** + * Seed nodes. + * + * This is defaulted to `{}` at the object-level. + * + * However Polykey-CLI will use use the `network` to fill this. + */ + seedNodes: {}, + /** + * Number of workers to spin up by default. + * + * Using `undefined` means all cores. Using `0` means no workers at all. + */ + workers: undefined, + /** + * If using dual stack `::`, then this forces only IPv6 bindings. + */ + ipv6Only: false, }, }; +type Config = typeof config; + export default config; + +export type { Config }; diff --git a/src/discovery/Discovery.ts b/src/discovery/Discovery.ts index 138abdfc8..224a0ffda 100644 --- a/src/discovery/Discovery.ts +++ b/src/discovery/Discovery.ts @@ -33,6 +33,7 @@ import { } from '@matrixai/async-init/dist/CreateDestroyStartStop'; import { timedCancellable, context } from '@matrixai/contexts/dist/decorators'; import * as discoveryErrors from './errors'; +import * as discoveryEvents from './events'; import * as tasksErrors from '../tasks/errors'; import * as gestaltsUtils from '../gestalts/utils'; import * as nodesUtils from '../nodes/utils'; @@ -60,6 +61,14 @@ interface Discovery extends CreateDestroyStartStop {} @CreateDestroyStartStop( new discoveryErrors.ErrorDiscoveryRunning(), new discoveryErrors.ErrorDiscoveryDestroyed(), + { + eventStart: discoveryEvents.EventDiscoveryStart, + eventStarted: discoveryEvents.EventDiscoveryStarted, + eventStop: discoveryEvents.EventDiscoveryStop, + eventStopped: discoveryEvents.EventDiscoveryStopped, + eventDestroy: discoveryEvents.EventDiscoveryDestroy, + eventDestroyed: discoveryEvents.EventDiscoveryDestroyed, + }, ) class Discovery { static async createDiscovery({ diff --git a/src/discovery/errors.ts b/src/discovery/errors.ts index ca83bfd18..3fa021a16 100644 --- a/src/discovery/errors.ts +++ b/src/discovery/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorDiscovery extends ErrorPolykey {} diff --git a/src/discovery/events.ts b/src/discovery/events.ts new file mode 100644 index 000000000..dcb9bdb8f --- /dev/null +++ b/src/discovery/events.ts @@ -0,0 +1,25 @@ +import EventPolykey from '../EventPolykey'; + +abstract class EventDiscovery extends EventPolykey {} + +class EventDiscoveryStart extends EventDiscovery {} + +class EventDiscoveryStarted extends EventDiscovery {} + +class EventDiscoveryStop extends EventDiscovery {} + +class EventDiscoveryStopped extends EventDiscovery {} + +class EventDiscoveryDestroy extends EventDiscovery {} + +class EventDiscoveryDestroyed extends EventDiscovery {} + +export { + EventDiscovery, + EventDiscoveryStart, + EventDiscoveryStarted, + EventDiscoveryStop, + EventDiscoveryStopped, + EventDiscoveryDestroy, + EventDiscoveryDestroyed, +}; diff --git a/src/discovery/index.ts b/src/discovery/index.ts index 77f52a73e..0ec4279de 100644 --- a/src/discovery/index.ts +++ b/src/discovery/index.ts @@ -1,2 +1,3 @@ export { default as Discovery } from './Discovery'; export * as errors from './errors'; +export * as events from './events'; diff --git a/src/errors.ts b/src/errors.ts index a612fb9ce..fc807039a 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -65,9 +65,9 @@ export * from './sessions/errors'; export * from './keys/errors'; export * from './vaults/errors'; export * from './git/errors'; +export * from './discovery/errors'; export * from './gestalts/errors'; export * from './identities/errors'; -export * from './agent/errors'; export * from './client/errors'; export * from './network/errors'; export * from './nodes/errors'; @@ -77,6 +77,9 @@ export * from './bootstrap/errors'; export * from './notifications/errors'; export * from './schema/errors'; export * from './status/errors'; +export * from './tasks/errors'; +export * from './tokens/errors'; export * from './validation/errors'; export * from './utils/errors'; export * from './rpc/errors'; +export * from './workers/errors'; diff --git a/src/events.ts b/src/events.ts new file mode 100644 index 000000000..ed34e1a80 --- /dev/null +++ b/src/events.ts @@ -0,0 +1,66 @@ +import EventPolykey from './EventPolykey'; + +abstract class EventPolykeyAgent extends EventPolykey {} + +class EventPolykeyAgentStart extends EventPolykeyAgent {} + +class EventPolykeyAgentStarted extends EventPolykeyAgent {} + +class EventPolykeyAgentStop extends EventPolykeyAgent {} + +class EventPolykeyAgentStopped extends EventPolykeyAgent {} + +class EventPolykeyAgentDestroy extends EventPolykeyAgent {} + +class EventPolykeyAgentDestroyed extends EventPolykeyAgent {} + +abstract class EventPolykeyClient extends EventPolykey {} + +class EventPolykeyClientStart extends EventPolykeyClient {} + +class EventPolykeyClientStarted extends EventPolykeyClient {} + +class EventPolykeyClientStop extends EventPolykeyClient {} + +class EventPolykeyClientStopped extends EventPolykeyClient {} + +class EventPolykeyClientDestroy extends EventPolykeyClient {} + +class EventPolykeyClientDestroyed extends EventPolykeyClient {} + +export { + EventPolykeyAgent, + EventPolykeyAgentStart, + EventPolykeyAgentStarted, + EventPolykeyAgentStop, + EventPolykeyAgentStopped, + EventPolykeyAgentDestroy, + EventPolykeyAgentDestroyed, + EventPolykeyClient, + EventPolykeyClientStart, + EventPolykeyClientStarted, + EventPolykeyClientStop, + EventPolykeyClientStopped, + EventPolykeyClientDestroy, + EventPolykeyClientDestroyed, +}; + +/** + * Recursively export all domain-level events classes + * This ensures that we have one place to construct and + * reference all Polykey events. + */ +export * from './acl/events'; +export * from './discovery/events'; +export * from './sessions/events'; +export * from './keys/events'; +export * from './vaults/events'; +export * from './gestalts/events'; +export * from './identities/events'; +export * from './nodes/events'; +export * from './sigchain/events'; +export * from './notifications/events'; +export * from './schema/events'; +export * from './status/events'; +export * from './tasks/events'; +export * from './rpc/events'; diff --git a/src/events/EventBus.ts b/src/events/EventBus.ts deleted file mode 100644 index 9870e06e6..000000000 --- a/src/events/EventBus.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { EventEmitter } from 'events'; - -class EventBus extends EventEmitter { - protected kCapture: symbol; - - constructor(...args: ConstructorParameters) { - super(...args); - // EventEmitter's captureRejections option is only accessible through a private symbol - // Here we augment the construction and save it as a property - const symbols = Object.getOwnPropertySymbols(this); - this.kCapture = symbols[0]; - } - - public async emitAsync( - event: string | symbol, - ...args: Array - ): Promise { - const listeners = this.rawListeners(event); - if (listeners.length < 1) { - return false; - } - let result; - try { - for (const listener of listeners) { - result = listener.apply(this, args); - await result; - } - } catch (e) { - if (!this[this.kCapture]) { - throw e; - } - // The capture rejections mechanism only applies to promises - if (!(result instanceof Promise)) { - throw e; - } - // Queues error handling asynchronously to avoid bubbling up rejections - // This matches the behaviour of EventEmitter which uses `process.nextTick` - queueMicrotask(() => { - if (typeof this[EventEmitter.captureRejectionSymbol] === 'function') { - this[EventEmitter.captureRejectionSymbol](e, event, ...args); - } else { - // Disable the capture rejections mechanism to avoid infinite loop - const prev = this[this.kCapture]; - // If the error handler throws, it results in `uncaughtException` - try { - this[this.kCapture] = false; - this.emit('error', e); - } finally { - this[this.kCapture] = prev; - } - } - }); - } - return true; - } -} - -export default EventBus; diff --git a/src/events/index.ts b/src/events/index.ts deleted file mode 100644 index 53f2dea6d..000000000 --- a/src/events/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { captureRejectionSymbol } from 'events'; -export { default as EventBus } from './EventBus'; diff --git a/src/gestalts/GestaltGraph.ts b/src/gestalts/GestaltGraph.ts index c85bfd3dd..0c9620e81 100644 --- a/src/gestalts/GestaltGraph.ts +++ b/src/gestalts/GestaltGraph.ts @@ -25,6 +25,7 @@ import { import { IdInternal } from '@matrixai/id'; import * as gestaltsUtils from './utils'; import * as gestaltsErrors from './errors'; +import * as gestaltsEvents from './events'; import * as aclUtils from '../acl/utils'; import { never } from '../utils'; @@ -32,6 +33,14 @@ interface GestaltGraph extends CreateDestroyStartStop {} @CreateDestroyStartStop( new gestaltsErrors.ErrorGestaltsGraphRunning(), new gestaltsErrors.ErrorGestaltsGraphDestroyed(), + { + eventStart: gestaltsEvents.EventGestaltsStart, + eventStarted: gestaltsEvents.EventGestaltsStarted, + eventStop: gestaltsEvents.EventGestaltsStop, + eventStopped: gestaltsEvents.EventGestaltsStopped, + eventDestroy: gestaltsEvents.EventGestaltsDestroy, + eventDestroyed: gestaltsEvents.EventGestaltsDestroyed, + }, ) class GestaltGraph { static async createGestaltGraph({ diff --git a/src/gestalts/errors.ts b/src/gestalts/errors.ts index b05a63cfe..b43f09e25 100644 --- a/src/gestalts/errors.ts +++ b/src/gestalts/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorGestalts extends ErrorPolykey {} diff --git a/src/gestalts/events.ts b/src/gestalts/events.ts new file mode 100644 index 000000000..63efa30f4 --- /dev/null +++ b/src/gestalts/events.ts @@ -0,0 +1,25 @@ +import EventPolykey from '../EventPolykey'; + +abstract class EventGestalts extends EventPolykey {} + +class EventGestaltsStart extends EventGestalts {} + +class EventGestaltsStarted extends EventGestalts {} + +class EventGestaltsStop extends EventGestalts {} + +class EventGestaltsStopped extends EventGestalts {} + +class EventGestaltsDestroy extends EventGestalts {} + +class EventGestaltsDestroyed extends EventGestalts {} + +export { + EventGestalts, + EventGestaltsStart, + EventGestaltsStarted, + EventGestaltsStop, + EventGestaltsStopped, + EventGestaltsDestroy, + EventGestaltsDestroyed, +}; diff --git a/src/gestalts/index.ts b/src/gestalts/index.ts index 28d038b8a..8444b2131 100644 --- a/src/gestalts/index.ts +++ b/src/gestalts/index.ts @@ -2,3 +2,4 @@ export { default as GestaltGraph } from './GestaltGraph'; export * as utils from './utils'; export * as types from './types'; export * as errors from './errors'; +export * as events from './events'; diff --git a/src/git/errors.ts b/src/git/errors.ts index e2f265e32..cc8ac2041 100644 --- a/src/git/errors.ts +++ b/src/git/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorGit extends ErrorPolykey {} diff --git a/src/identities/IdentitiesManager.ts b/src/identities/IdentitiesManager.ts index dcd1c5237..cff466ca7 100644 --- a/src/identities/IdentitiesManager.ts +++ b/src/identities/IdentitiesManager.ts @@ -18,6 +18,7 @@ import { } from '@matrixai/async-init/dist/CreateDestroyStartStop'; import Logger from '@matrixai/logger'; import * as identitiesErrors from './errors'; +import * as identitiesEvents from './events'; import * as nodesUtils from '../nodes/utils'; import { promise } from '../utils/index'; import { encodeProviderIdentityId } from '../ids'; @@ -26,6 +27,14 @@ interface IdentitiesManager extends CreateDestroyStartStop {} @CreateDestroyStartStop( new identitiesErrors.ErrorIdentitiesManagerRunning(), new identitiesErrors.ErrorIdentitiesManagerDestroyed(), + { + eventStart: identitiesEvents.EventIdentitiesManagerStart, + eventStarted: identitiesEvents.EventIdentitiesManagerStarted, + eventStop: identitiesEvents.EventIdentitiesManagerStop, + eventStopped: identitiesEvents.EventIdentitiesManagerStopped, + eventDestroy: identitiesEvents.EventIdentitiesManagerDestroy, + eventDestroyed: identitiesEvents.EventIdentitiesManagerDestroyed, + }, ) class IdentitiesManager { static async createIdentitiesManager({ diff --git a/src/identities/errors.ts b/src/identities/errors.ts index 6857a2c5d..3a2d8ff64 100644 --- a/src/identities/errors.ts +++ b/src/identities/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorIdentities extends ErrorPolykey {} diff --git a/src/identities/events.ts b/src/identities/events.ts new file mode 100644 index 000000000..b5068bacb --- /dev/null +++ b/src/identities/events.ts @@ -0,0 +1,25 @@ +import EventPolykey from '../EventPolykey'; + +abstract class EventIdentitiesManager extends EventPolykey {} + +class EventIdentitiesManagerStart extends EventIdentitiesManager {} + +class EventIdentitiesManagerStarted extends EventIdentitiesManager {} + +class EventIdentitiesManagerStop extends EventIdentitiesManager {} + +class EventIdentitiesManagerStopped extends EventIdentitiesManager {} + +class EventIdentitiesManagerDestroy extends EventIdentitiesManager {} + +class EventIdentitiesManagerDestroyed extends EventIdentitiesManager {} + +export { + EventIdentitiesManager, + EventIdentitiesManagerStart, + EventIdentitiesManagerStarted, + EventIdentitiesManagerStop, + EventIdentitiesManagerStopped, + EventIdentitiesManagerDestroy, + EventIdentitiesManagerDestroyed, +}; diff --git a/src/identities/index.ts b/src/identities/index.ts index 0d9c28599..1cdbc8d26 100644 --- a/src/identities/index.ts +++ b/src/identities/index.ts @@ -3,4 +3,5 @@ export { default as Provider } from './Provider'; export * as utils from './utils'; export * as types from './types'; export * as errors from './errors'; +export * as events from './events'; export * as providers from './providers'; diff --git a/src/index.ts b/src/index.ts index 25c85c720..3b0cd327e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,10 @@ export { default as PolykeyAgent } from './PolykeyAgent'; export { default as PolykeyClient } from './PolykeyClient'; +export { default as EventPolykey } from './EventPolykey'; export { default as ErrorPolykey } from './ErrorPolykey'; export { default as config } from './config'; export * as utils from './utils'; +export * as events from './events'; export * as errors from './errors'; export * from './types'; @@ -11,12 +13,10 @@ export * from './types'; // kitchen sink here export * as acl from './acl'; -export * as agent from './agent'; export * as bootstrap from './bootstrap'; export * as claims from './claims'; export * as client from './client'; export * as discovery from './discovery'; -export * as events from './events'; export * as gestalts from './gestalts'; export * as git from './git'; export * as http from './http'; diff --git a/src/keys/CertManager.ts b/src/keys/CertManager.ts index bafb4df86..830c9410c 100644 --- a/src/keys/CertManager.ts +++ b/src/keys/CertManager.ts @@ -4,11 +4,11 @@ import type { PrivateKey, Certificate, CertificateASN1, - CertManagerChangeData, CertificatePEM, KeyPair, RecoveryCode, CertificatePEMChain, + CertManagerOptions, } from './types'; import type KeyRing from './KeyRing'; import type TaskManager from '../tasks/TaskManager'; @@ -24,7 +24,10 @@ import { import { Lock } from '@matrixai/async-locks'; import * as keysUtils from './utils'; import * as keysErrors from './errors'; +import * as keysEvents from './events'; import * as ids from '../ids'; +import * as utils from '../utils/utils'; +import config from '../config'; /** * This signal reason indicates we want to stop the renewal @@ -37,6 +40,14 @@ interface CertManager extends CreateDestroyStartStop {} @CreateDestroyStartStop( new keysErrors.ErrorCertManagerRunning(), new keysErrors.ErrorCertManagerDestroyed(), + { + eventStart: keysEvents.EventCertManagerStart, + eventStarted: keysEvents.EventCertManagerStarted, + eventStop: keysEvents.EventCertManagerStop, + eventStopped: keysEvents.EventCertManagerStopped, + eventDestroy: keysEvents.EventCertManagerDestroy, + eventDestroyed: keysEvents.EventCertManagerDestroyed, + }, ) class CertManager { /** @@ -57,9 +68,7 @@ class CertManager { db, keyRing, taskManager, - certDuration = 31536000, - certRenewLeadTime = 86400, - changeCallback, + options = {}, workerManager, logger = new Logger(this.name), subjectAttrsExtra, @@ -70,9 +79,7 @@ class CertManager { db: DB; keyRing: KeyRing; taskManager: TaskManager; - certDuration?: number; - certRenewLeadTime?: number; - changeCallback?: (data: CertManagerChangeData) => any; + options: Partial; workerManager?: PolykeyWorkerManagerInterface; logger?: Logger; subjectAttrsExtra?: Array<{ [key: string]: Array }>; @@ -82,13 +89,15 @@ class CertManager { fresh?: boolean; }): Promise { logger.info(`Creating ${this.name}`); + const optionsDefaulted = utils.mergeObjects(options, { + certDuration: config.defaultsUser.certDuration, + certRenewLeadTime: config.defaultsUser.certRenewLeadTime, + }) as CertManagerOptions; const certManager = new this({ db, keyRing, taskManager, - certDuration, - certRenewLeadTime, - changeCallback, + options: optionsDefaulted, workerManager, logger, }); @@ -112,7 +121,6 @@ class CertManager { protected db: DB; protected keyRing: KeyRing; protected taskManager: TaskManager; - protected changeCallback?: (data: CertManagerChangeData) => any; protected workerManager?: PolykeyWorkerManagerInterface; protected generateCertId: () => CertId; protected dbPath: LevelPath = [this.constructor.name]; @@ -145,18 +153,14 @@ class CertManager { db, keyRing, taskManager, - certDuration, - certRenewLeadTime, - changeCallback, + options, workerManager, logger, }: { db: DB; keyRing: KeyRing; taskManager: TaskManager; - certDuration: number; - certRenewLeadTime: number; - changeCallback?: (data: CertManagerChangeData) => any; + options: CertManagerOptions; workerManager?: PolykeyWorkerManagerInterface; logger: Logger; }) { @@ -164,9 +168,8 @@ class CertManager { this.db = db; this.keyRing = keyRing; this.taskManager = taskManager; - this.certDuration = certDuration; - this.certRenewLeadTime = certRenewLeadTime; - this.changeCallback = changeCallback; + this.certDuration = options.certDuration; + this.certRenewLeadTime = options.certRenewLeadTime; this.workerManager = workerManager; } @@ -452,14 +455,16 @@ class CertManager { if (this.tasksRunning) { await this.setupRenewCurrentCertTask(now); } - if (this.changeCallback != null) { - await this.changeCallback({ - nodeId: this.keyRing.getNodeId(), - keyPair: this.keyRing.keyPair, - cert: certNew, - recoveryCode: recoveryCodeNew!, - }); - } + this.dispatchEvent( + new keysEvents.EventCertManagerCertChange({ + detail: { + nodeId: this.keyRing.getNodeId(), + keyPair: this.keyRing.keyPair, + cert: certNew, + recoveryCode: recoveryCodeNew!, + }, + }), + ); this.logger.info('Renewed certificate chain with new key pair'); }); return certNew!; @@ -516,14 +521,16 @@ class CertManager { if (this.tasksRunning) { await this.setupRenewCurrentCertTask(now); } - if (this.changeCallback != null) { - await this.changeCallback({ - nodeId: this.keyRing.getNodeId(), - keyPair: this.keyRing.keyPair, - cert: certNew, - recoveryCode: undefined, - }); - } + this.dispatchEvent( + new keysEvents.EventCertManagerCertChange({ + detail: { + nodeId: this.keyRing.getNodeId(), + keyPair: this.keyRing.keyPair, + cert: certNew, + recoveryCode: undefined, + }, + }), + ); this.logger.info('Renewed certificate chain with current key pair'); }); return certNew!; @@ -587,14 +594,16 @@ class CertManager { if (this.tasksRunning) { await this.setupRenewCurrentCertTask(now); } - if (this.changeCallback != null) { - await this.changeCallback({ - nodeId: this.keyRing.getNodeId(), - keyPair: this.keyRing.keyPair, - cert: certNew!, - recoveryCode: recoveryCodeNew!, - }); - } + this.dispatchEvent( + new keysEvents.EventCertManagerCertChange({ + detail: { + nodeId: this.keyRing.getNodeId(), + keyPair: this.keyRing.keyPair, + cert: certNew!, + recoveryCode: recoveryCodeNew!, + }, + }), + ); this.logger.info('Resetted certificate chain with new key pair'); }); return certNew!; @@ -646,13 +655,16 @@ class CertManager { if (this.tasksRunning) { await this.setupRenewCurrentCertTask(now); } - if (this.changeCallback != null) { - await this.changeCallback({ - nodeId: this.keyRing.getNodeId(), - keyPair: this.keyRing.keyPair, - cert: certNew, - }); - } + this.dispatchEvent( + new keysEvents.EventCertManagerCertChange({ + detail: { + nodeId: this.keyRing.getNodeId(), + keyPair: this.keyRing.keyPair, + cert: certNew, + recoveryCode: undefined, + }, + }), + ); this.logger.info('Resetted certificate chain with current key pair'); }); return certNew!; diff --git a/src/keys/KeyRing.ts b/src/keys/KeyRing.ts index f023d11f1..0f1587633 100644 --- a/src/keys/KeyRing.ts +++ b/src/keys/KeyRing.ts @@ -12,6 +12,7 @@ import type { RecoveryCodeLocked, PasswordOpsLimit, PasswordMemLimit, + KeyRingOptions, } from './types'; import type { NodeId } from '../ids/types'; import type { PolykeyWorkerManagerInterface } from '../workers/types'; @@ -25,57 +26,54 @@ import { import { Lock } from '@matrixai/async-locks'; import * as keysUtils from './utils'; import * as keysErrors from './errors'; +import * as keysEvents from './events'; import { bufferLock, bufferUnlock } from './utils/memory'; +import * as utils from '../utils/utils'; interface KeyRing extends CreateDestroyStartStop {} @CreateDestroyStartStop( new keysErrors.ErrorKeyRingRunning(), new keysErrors.ErrorKeyRingDestroyed(), + { + eventStart: keysEvents.EventKeyRingStart, + eventStarted: keysEvents.EventKeyRingStarted, + eventStop: keysEvents.EventKeyRingStop, + eventStopped: keysEvents.EventKeyRingStopped, + eventDestroy: keysEvents.EventKeyRingDestroy, + eventDestroyed: keysEvents.EventKeyRingDestroyed, + }, ) class KeyRing { public static async createKeyRing({ keysPath, + password, + options = {}, workerManager, - passwordOpsLimit, - passwordMemLimit, - strictMemoryLock = true, fs = require('fs'), logger = new Logger(this.name), - ...startOptions + fresh, }: { keysPath: string; password: string; + options: Partial; workerManager?: PolykeyWorkerManagerInterface; - passwordOpsLimit?: PasswordOpsLimit; - passwordMemLimit?: PasswordMemLimit; - strictMemoryLock?: boolean; fs?: FileSystem; logger?: Logger; fresh?: boolean; - } & ( // eslint-disable-next-line @typescript-eslint/ban-types - | {} - | { - recoveryCode: RecoveryCode; - } - | { - privateKey: PrivateKey; - } - | { - privateKeyPath: string; - } - )): Promise { + }): Promise { logger.info(`Creating ${this.name}`); logger.info(`Setting keys path to ${keysPath}`); + const optionsDefaulted = utils.mergeObjects(options, { + strictMemoryLock: true, + }) as KeyRingOptions; const keyRing = new this({ keysPath, workerManager, - passwordOpsLimit, - passwordMemLimit, - strictMemoryLock, + options: optionsDefaulted, fs, logger, }); - await keyRing.start(startOptions); + await keyRing.start({ password, fresh }); logger.info(`Created ${this.name}`); return keyRing; } @@ -103,17 +101,13 @@ class KeyRing { public constructor({ keysPath, workerManager, - passwordOpsLimit, - passwordMemLimit, - strictMemoryLock, + options, fs, logger, }: { keysPath: string; workerManager?: PolykeyWorkerManagerInterface; - passwordOpsLimit?: PasswordOpsLimit; - passwordMemLimit?: PasswordMemLimit; - strictMemoryLock: boolean; + options: KeyRingOptions; fs: FileSystem; logger: Logger; }) { @@ -121,9 +115,9 @@ class KeyRing { this.keysPath = keysPath; this.workerManager = workerManager; this.fs = fs; - this.passwordOpsLimit = passwordOpsLimit; - this.passwordMemLimit = passwordMemLimit; - this.strictMemoryLock = strictMemoryLock; + this.passwordOpsLimit = options.passwordOpsLimit; + this.passwordMemLimit = options.passwordMemLimit; + this.strictMemoryLock = options.strictMemoryLock; this.publicKeyPath = path.join(keysPath, 'public.jwk'); this.privateKeyPath = path.join(keysPath, 'private.jwk'); this.dbKeyPath = path.join(keysPath, 'db.jwk'); @@ -157,9 +151,8 @@ class KeyRing { }); } await this.fs.promises.mkdir(this.keysPath, { recursive: true }); - const [keyPair, recoveryCode] = await this.setupKeyPair( - setupKeyPairOptions, - ); + const [keyPair, recoveryCode] = + await this.setupKeyPair(setupKeyPairOptions); const dbKey = await this.setupDbKey(keyPair); const [passwordHash, passwordSalt] = await this.setupPasswordHash( options.password, @@ -280,9 +273,8 @@ class KeyRing { await this.rotateLock.withF(async () => { this.logger.info('Changing root key pair password'); await this.writeKeyPair(this._keyPair!, password); - const [passwordHash, passwordSalt] = await this.setupPasswordHash( - password, - ); + const [passwordHash, passwordSalt] = + await this.setupPasswordHash(password); bufferUnlock(this.passwordHash!.hash); bufferUnlock(this.passwordHash!.salt); bufferLock(passwordHash, this.strictMemoryLock); @@ -372,9 +364,8 @@ class KeyRing { bufferUnlock(this._recoveryCodeData); } this._recoveryCodeData = recoveryCodeData as RecoveryCodeLocked; - const [passwordHash, passwordSalt] = await this.setupPasswordHash( - password, - ); + const [passwordHash, passwordSalt] = + await this.setupPasswordHash(password); bufferUnlock(this.passwordHash!.hash); bufferUnlock(this.passwordHash!.salt); bufferLock(passwordHash, this.strictMemoryLock); diff --git a/src/keys/errors.ts b/src/keys/errors.ts index eeed0cf46..3a20d9e86 100644 --- a/src/keys/errors.ts +++ b/src/keys/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorKeys extends ErrorPolykey {} diff --git a/src/keys/events.ts b/src/keys/events.ts new file mode 100644 index 000000000..53a1aa0c1 --- /dev/null +++ b/src/keys/events.ts @@ -0,0 +1,53 @@ +import type { CertManagerChangeData } from './types'; +import EventPolykey from '../EventPolykey'; + +abstract class EventKeys extends EventPolykey {} + +abstract class EventCertManager extends EventKeys {} + +class EventCertManagerStart extends EventCertManager {} + +class EventCertManagerStarted extends EventCertManager {} + +class EventCertManagerStop extends EventCertManager {} + +class EventCertManagerStopped extends EventCertManager {} + +class EventCertManagerDestroy extends EventCertManager {} + +class EventCertManagerDestroyed extends EventCertManager {} + +class EventCertManagerCertChange extends EventCertManager {} + +abstract class EventKeyRing extends EventKeys {} + +class EventKeyRingStart extends EventKeyRing {} + +class EventKeyRingStarted extends EventKeyRing {} + +class EventKeyRingStop extends EventKeyRing {} + +class EventKeyRingStopped extends EventKeyRing {} + +class EventKeyRingDestroy extends EventKeyRing {} + +class EventKeyRingDestroyed extends EventKeyRing {} + +export { + EventKeys, + EventCertManager, + EventCertManagerStart, + EventCertManagerStarted, + EventCertManagerStop, + EventCertManagerStopped, + EventCertManagerDestroy, + EventCertManagerDestroyed, + EventCertManagerCertChange, + EventKeyRing, + EventKeyRingStart, + EventKeyRingStarted, + EventKeyRingStop, + EventKeyRingStopped, + EventKeyRingDestroy, + EventKeyRingDestroyed, +}; diff --git a/src/keys/index.ts b/src/keys/index.ts index 51eda9cab..47b2aba46 100644 --- a/src/keys/index.ts +++ b/src/keys/index.ts @@ -3,3 +3,4 @@ export { default as CertManager } from './CertManager'; export * as utils from './utils'; export * as types from './types'; export * as errors from './errors'; +export * as events from './events'; diff --git a/src/keys/types.ts b/src/keys/types.ts index 19ca1ee76..c8ba9da88 100644 --- a/src/keys/types.ts +++ b/src/keys/types.ts @@ -1,6 +1,6 @@ import type { X509Certificate } from '@peculiar/x509'; import type { NodeId } from '../ids/types'; -import type { Opaque, InverseRecord } from '../types'; +import type { Opaque, InverseRecord, ObjectEmpty } from '../types'; /** * Locked buffer wrapper type for sensitive in-memory data. @@ -262,6 +262,39 @@ type CertManagerChangeData = { recoveryCode?: RecoveryCode; }; +/** + * Used by the PolykeyAgent for it's top level options + */ +type KeysOptions = KeyRingOptions & CertManagerOptions; + +/** + * Options for the KeyRing + */ +type KeyRingOptions = { + passwordOpsLimit?: PasswordOpsLimit; + passwordMemLimit?: PasswordMemLimit; + strictMemoryLock: boolean; +} & ( + | ObjectEmpty + | { + recoveryCode: RecoveryCode; + } + | { + privateKey: PrivateKey; + } + | { + privateKeyPath: string; + } +); + +/** + * Options for the CertManager + */ +type CertManagerOptions = { + certDuration: number; + certRenewLeadTime: number; +}; + export type { BufferLocked, Key, @@ -298,6 +331,9 @@ export type { CertificatePEM, CertificatePEMChain, CertManagerChangeData, + KeysOptions, + KeyRingOptions, + CertManagerOptions, }; export type { CertId, CertIdString, CertIdEncoded } from '../ids/types'; diff --git a/src/keys/utils/webcrypto.ts b/src/keys/utils/webcrypto.ts index a89631a7a..0fe9d4955 100644 --- a/src/keys/utils/webcrypto.ts +++ b/src/keys/utils/webcrypto.ts @@ -1,17 +1,18 @@ import type { PublicKey, PrivateKey, KeyPair } from '../types'; import sodium from 'sodium-native'; -import { Crypto } from '@peculiar/webcrypto'; +import * as peculiarWebcrypto from '@peculiar/webcrypto'; import * as utils from '../../utils'; /** * WebCrypto polyfill from @peculiar/webcrypto * This behaves differently with respect to Ed25519 keys * See: https://github.com/PeculiarVentures/webcrypto/issues/55 + * Note that the types don't exactly match but it is compatible. */ -const webcrypto = new Crypto(); +const webcrypto = new peculiarWebcrypto.Crypto(); /** - * Monkey patches the global crypto object polyfill + * Monkey patches the global crypto object polyfill. */ globalThis.crypto = webcrypto; diff --git a/src/network/errors.ts b/src/network/errors.ts index 34addbda9..4adb5beaa 100644 --- a/src/network/errors.ts +++ b/src/network/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorNetwork extends ErrorPolykey {} diff --git a/src/network/utils.ts b/src/network/utils.ts index e71c806b4..4c835e331 100644 --- a/src/network/utils.ts +++ b/src/network/utils.ts @@ -1,15 +1,16 @@ import type { PromiseCancellable } from '@matrixai/async-cancellable'; import type { ContextTimed } from '@matrixai/contexts'; -import type { Host, Hostname, Port, Address } from './types'; -import type { Certificate } from '../keys/types'; +import type { Address, Host, Hostname, Port } from './types'; +import type { Certificate, CertificatePEM } from '../keys/types'; import type { NodeId } from '../ids/types'; import type { NodeAddress } from '../nodes/types'; -import type { CertificatePEM } from '../keys/types'; +import type { X509Certificate } from '@peculiar/x509'; import dns from 'dns'; import { IPv4, IPv6, Validator } from 'ip-num'; import { timedCancellable } from '@matrixai/contexts/dist/functions'; +import { CryptoError } from '@matrixai/quic/dist/native'; +import { utils as quicUtils } from '@matrixai/quic'; import * as networkErrors from './errors'; -import { never } from '../utils'; import * as keysUtils from '../keys/utils'; /** @@ -217,21 +218,38 @@ function resolvesZeroIP(ip: Host): Host { */ async function verifyServerCertificateChain( nodeIds: Array, - certPEMChain: Array, -): Promise { + certs: Array, +): Promise< + | { + result: 'success'; + nodeId: NodeId; + } + | { + result: 'fail'; + value: CryptoError; + } +> { + const certPEMChain = certs.map((v) => quicUtils.derToPEM(v)); if (certPEMChain.length === 0) { - throw new networkErrors.ErrorCertChainEmpty( - 'No certificates available to verify', - ); + return { + result: 'fail', + value: CryptoError.CertificateRequired, + }; } if (nodeIds.length === 0) { throw new networkErrors.ErrorConnectionNodesEmpty(); } - const certChain = certPEMChain.map((v) => { - const cert = keysUtils.certFromPEM(v as CertificatePEM); - if (cert == null) never(); - return cert; - }); + const certChain: Array> = []; + for (const certPEM of certPEMChain) { + const cert = keysUtils.certFromPEM(certPEM as CertificatePEM); + if (cert == null) { + return { + result: 'fail', + value: CryptoError.BadCertificate, + }; + } + certChain.push(cert); + } const now = new Date(); let certClaim: Certificate | null = null; let certClaimIndex: number | null = null; @@ -239,55 +257,30 @@ async function verifyServerCertificateChain( for (let certIndex = 0; certIndex < certChain.length; certIndex++) { const cert = certChain[certIndex]; if (now < cert.notBefore || now > cert.notAfter) { - throw new networkErrors.ErrorCertChainDateInvalid( - 'Chain certificate date is invalid', - { - data: { - cert, - certIndex, - notBefore: cert.notBefore, - notAfter: cert.notAfter, - now, - }, - }, - ); + return { + result: 'fail', + value: CryptoError.CertificateExpired, + }; } const certNodeId = keysUtils.certNodeId(cert); if (certNodeId == null) { - throw new networkErrors.ErrorCertChainNameInvalid( - 'Chain certificate common name attribute is missing', - { - data: { - cert, - certIndex, - }, - }, - ); + return { + result: 'fail', + value: CryptoError.BadCertificate, + }; } const certPublicKey = keysUtils.certPublicKey(cert); if (certPublicKey == null) { - throw new networkErrors.ErrorCertChainKeyInvalid( - 'Chain certificate public key is missing', - { - data: { - cert, - certIndex, - }, - }, - ); + return { + result: 'fail', + value: CryptoError.BadCertificate, + }; } if (!(await keysUtils.certNodeSigned(cert))) { - throw new networkErrors.ErrorCertChainSignatureInvalid( - 'Chain certificate does not have a valid node-signature', - { - data: { - cert, - certIndex, - nodeId: keysUtils.publicKeyToNodeId(certPublicKey), - commonName: certNodeId, - }, - }, - ); + return { + result: 'fail', + value: CryptoError.BadCertificate, + }; } for (const nodeId of nodeIds) { if (certNodeId.equals(nodeId)) { @@ -301,12 +294,10 @@ async function verifyServerCertificateChain( if (verifiedNodeId != null) break; } if (certClaimIndex == null || certClaim == null || verifiedNodeId == null) { - throw new networkErrors.ErrorCertChainUnclaimed( - 'Node IDs is not claimed by any certificate', - { - data: { nodeIds }, - }, - ); + return { + result: 'fail', + value: CryptoError.BadCertificate, + }; } if (certClaimIndex > 0) { let certParent: Certificate; @@ -321,20 +312,17 @@ async function verifyServerCertificateChain( keysUtils.certPublicKey(certChild)!, )) ) { - throw new networkErrors.ErrorCertChainBroken( - 'Chain certificate is not signed by parent certificate', - { - data: { - cert: certChild, - certIndex: certIndex - 1, - certParent, - }, - }, - ); + return { + result: 'fail', + value: CryptoError.BadCertificate, + }; } } } - return verifiedNodeId; + return { + result: 'success', + nodeId: verifiedNodeId, + }; } /** @@ -342,72 +330,35 @@ async function verifyServerCertificateChain( * The server does have a target NodeId. This means we verify the entire chain. */ async function verifyClientCertificateChain( - certPEMChain: Array, -): Promise { + certs: Array, +): Promise { + const certPEMChain = certs.map((v) => quicUtils.derToPEM(v)); if (certPEMChain.length === 0) { - throw new networkErrors.ErrorCertChainEmpty( - 'No certificates available to verify', - ); + return CryptoError.CertificateRequired; + } + const certChain: Array> = []; + for (const certPEM of certPEMChain) { + const cert = keysUtils.certFromPEM(certPEM as CertificatePEM); + if (cert == null) return CryptoError.BadCertificate; + certChain.push(cert); } - const certChain = certPEMChain.map((v) => { - const cert = keysUtils.certFromPEM(v as CertificatePEM); - if (cert == null) never(); - return cert; - }); const now = new Date(); for (let certIndex = 0; certIndex < certChain.length; certIndex++) { const cert = certChain[certIndex]; const certNext = certChain[certIndex + 1]; if (now < cert.notBefore || now > cert.notAfter) { - throw new networkErrors.ErrorCertChainDateInvalid( - 'Chain certificate date is invalid', - { - data: { - cert, - certIndex, - notBefore: cert.notBefore, - notAfter: cert.notAfter, - now, - }, - }, - ); + return CryptoError.CertificateExpired; } const certNodeId = keysUtils.certNodeId(cert); if (certNodeId == null) { - throw new networkErrors.ErrorCertChainNameInvalid( - 'Chain certificate common name attribute is missing', - { - data: { - cert, - certIndex, - }, - }, - ); + return CryptoError.BadCertificate; } const certPublicKey = keysUtils.certPublicKey(cert); if (certPublicKey == null) { - throw new networkErrors.ErrorCertChainKeyInvalid( - 'Chain certificate public key is missing', - { - data: { - cert, - certIndex, - }, - }, - ); + return CryptoError.BadCertificate; } if (!(await keysUtils.certNodeSigned(cert))) { - throw new networkErrors.ErrorCertChainSignatureInvalid( - 'Chain certificate does not have a valid node-signature', - { - data: { - cert, - certIndex, - nodeId: keysUtils.publicKeyToNodeId(certPublicKey), - commonName: certNodeId, - }, - }, - ); + return CryptoError.BadCertificate; } if (certNext != null) { if ( @@ -417,19 +368,12 @@ async function verifyClientCertificateChain( keysUtils.certPublicKey(cert)!, )) ) { - throw new networkErrors.ErrorCertChainSignatureInvalid( - 'Chain certificate is not signed by parent certificate', - { - data: { - cert, - certIndex, - certParent: certNext, - }, - }, - ); + return CryptoError.BadCertificate; } } } + // Undefined means success + return undefined; } /** diff --git a/src/nodes/NodeConnection.ts b/src/nodes/NodeConnection.ts index b0eae9da2..410d9d766 100644 --- a/src/nodes/NodeConnection.ts +++ b/src/nodes/NodeConnection.ts @@ -2,37 +2,40 @@ import type { ContextTimed } from '@matrixai/contexts'; import type { PromiseCancellable } from '@matrixai/async-cancellable'; import type { NodeId } from './types'; import type { Host, Hostname, Port, TLSConfig } from '../network/types'; -import type { Certificate, CertificatePEM } from '../keys/types'; -import type { ClientManifest, RPCStream } from '../rpc/types'; +import type { Certificate } from '../keys/types'; +import type { ClientManifest } from '../rpc/types'; import type { QUICSocket, - ClientCrypto, + ClientCryptoOps, QUICConnection, - events as quicEvents, } from '@matrixai/quic'; import type { ContextTimedInput } from '@matrixai/contexts/dist/types'; import type { X509Certificate } from '@peculiar/x509'; import Logger from '@matrixai/logger'; import { CreateDestroy } from '@matrixai/async-init/dist/CreateDestroy'; +import { status } from '@matrixai/async-init'; import { timedCancellable, context } from '@matrixai/contexts/dist/decorators'; -import { QUICClient } from '@matrixai/quic'; +import { AbstractEvent, EventAll } from '@matrixai/events'; +import { QUICClient, events as quicEvents } from '@matrixai/quic'; import * as nodesErrors from './errors'; import * as nodesEvents from './events'; import RPCClient from '../rpc/RPCClient'; import * as networkUtils from '../network/utils'; import * as rpcUtils from '../rpc/utils'; -import * as keysUtils from '../keys/utils'; import * as nodesUtils from '../nodes/utils'; import { never } from '../utils'; -import * as utils from '../utils'; +import config from '../config'; /** * Encapsulates the unidirectional client-side connection of one node to another. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- False positive for M interface NodeConnection extends CreateDestroy {} -@CreateDestroy() -class NodeConnection extends EventTarget { +@CreateDestroy({ + eventDestroy: nodesEvents.EventNodeConnectionDestroy, + eventDestroyed: nodesEvents.EventNodeConnectionDestroyed, +}) +class NodeConnection { /** * Hostname is defined if the target's host was resolved from this hostname * Undefined if a Host was directly provided @@ -57,39 +60,136 @@ class NodeConnection extends EventTarget { public readonly quicConnection: QUICConnection; public readonly rpcClient: RPCClient; + /** + * Dispatches a `EventNodeConnectionClose` in response to any `NodeConnection` + * error event. Will trigger destruction of the `NodeConnection` via the + * `EventNodeConnectionError` -> `EventNodeConnectionClose` event path. + */ + protected handleEventNodeConnectionError = ( + evt: nodesEvents.EventNodeConnectionError, + ): void => { + this.logger.warn(`NodeConnection error caused by ${evt.detail.message}`); + this.dispatchEvent(new nodesEvents.EventNodeConnectionClose()); + }; + + /** + * Triggers the destruction of the `NodeConnection`. Since this is only in + * response to an underlying problem or close it will force destroy. + * Dispatched by the `EventNodeConnectionError` event as the + * `EventNodeConnectionError` -> `EventNodeConnectionClose` event path. + */ + protected handleEventNodeConnectionClose = async ( + _evt: nodesEvents.EventNodeConnectionClose, + ): Promise => { + this.logger.warn(`close event triggering NodeConnection.destroy`); + // This will trigger the destruction of this NodeConnection. + if (this[status] !== 'destroying') { + await this.destroy({ force: true }); + } + }; + + /** + * Redispatches a `QUICStream` from a `EventQUICConnectionStream` event with + * a `EventNodeConnectionStream` event. Should bubble upwards through the + * `NodeConnectionManager`. + */ + protected handleEventQUICConnectionStream = ( + evt: quicEvents.EventQUICConnectionStream, + ): void => { + // Re-dispatches the stream under a `EventNodeConnectionStream` event + const quicStream = evt.detail; + this.dispatchEvent( + new nodesEvents.EventNodeConnectionStream({ detail: quicStream }), + ); + }; + + /** + * Redispatches `QUICConnection` or `QUICClient` error events as `NodeConnection` error events. + * This should trigger the destruction of the `NodeConnection` through the + * `EventNodeConnectionError` -> `EventNodeConnectionClose` event path. + */ + protected handleEventQUICError = ( + evt: quicEvents.EventQUICConnectionError, + ): void => { + const err = new nodesErrors.ErrorNodeConnectionInternalError(undefined, { + cause: evt.detail, + }); + this.dispatchEvent( + new nodesEvents.EventNodeConnectionError({ detail: err }), + ); + }; + + protected handleEventQUICClientDestroyed = ( + _evt: quicEvents.EventQUICClientDestroyed, + ) => { + const err = new nodesErrors.ErrorNodeConnectionInternalError( + 'QUICClient destroyed unexpectedly', + ); + this.dispatchEvent( + new nodesEvents.EventNodeConnectionError({ detail: err }), + ); + }; + + protected handleEventQUICConnectionStopped = ( + _evt: quicEvents.EventQUICConnectionStopped, + ) => { + const err = new nodesErrors.ErrorNodeConnectionInternalError( + 'QUICClient stopped unexpectedly', + ); + this.dispatchEvent( + new nodesEvents.EventNodeConnectionError({ detail: err }), + ); + }; + + /** + * Propagates all events from the `QUICClient` or `QUICConnection` upwards. + * If the `QUICClient` exists then it is just registered to that, otherwise just + * the `QUICConnection`. + * Will only re-dispatch the event if it's an `AbstractEvent`. + */ + protected handleEventAll = (evt: EventAll): void => { + // This just propagates events upwards + const event = evt.detail; + if (event instanceof AbstractEvent) { + // Clone and dispatch upwards + this.dispatchEvent(event.clone()); + } + }; + static createNodeConnection( { - handleStream, targetNodeIds, targetHost, targetPort, targetHostname, tlsConfig, connectionKeepAliveIntervalTime, - connectionMaxIdleTimeout = 60_000, + connectionKeepAliveTimeoutTime = config.defaultsSystem + .nodesConnectionIdleTimeoutTime, quicSocket, manifest, logger, }: { - handleStream: (stream: RPCStream) => void; targetNodeIds: Array; targetHost: Host; targetPort: Port; targetHostname?: Hostname; - crypto: ClientCrypto; + crypto: ClientCryptoOps; tlsConfig: TLSConfig; connectionKeepAliveIntervalTime?: number; - connectionMaxIdleTimeout?: number; + connectionKeepAliveTimeoutTime?: number; quicSocket?: QUICSocket; manifest: M; logger?: Logger; }, ctx?: Partial, ): PromiseCancellable>; - @timedCancellable(true, 20000) + @timedCancellable( + true, + config.defaultsSystem.nodesConnectionConnectTimeoutTime, + ) static async createNodeConnection( { - handleStream, targetNodeIds, targetHost, targetPort, @@ -98,21 +198,21 @@ class NodeConnection extends EventTarget { tlsConfig, manifest, connectionKeepAliveIntervalTime, - connectionMaxIdleTimeout = 60_000, + connectionKeepAliveTimeoutTime = config.defaultsSystem + .nodesConnectionIdleTimeoutTime, quicSocket, logger = new Logger(this.name), }: { - handleStream: (stream: RPCStream) => void; targetNodeIds: Array; targetHost: Host; targetPort: Port; targetHostname?: Hostname; - crypto: ClientCrypto; + crypto: ClientCryptoOps; tlsConfig: TLSConfig; manifest: M; connectionKeepAliveIntervalTime?: number; - connectionMaxIdleTimeout?: number; - quicSocket?: QUICSocket; + connectionKeepAliveTimeoutTime?: number; + quicSocket: QUICSocket; logger?: Logger; }, @context ctx: ContextTimed, @@ -130,52 +230,47 @@ class NodeConnection extends EventTarget { socket: quicSocket, config: { keepAliveIntervalTime: connectionKeepAliveIntervalTime, - maxIdleTimeout: connectionMaxIdleTimeout, + maxIdleTimeout: connectionKeepAliveTimeoutTime, verifyPeer: true, - verifyAllowFail: true, + verifyCallback: async (certPEMs) => { + const result = await networkUtils.verifyServerCertificateChain( + targetNodeIds, + certPEMs, + ); + if (result.result === 'success') { + validatedNodeId = result.nodeId; + return; + } else { + return result.value; + } + }, ca: undefined, key: tlsConfig.keyPrivatePem, cert: tlsConfig.certChainPem, }, - verifyCallback: async (certPEMs) => { - validatedNodeId = await networkUtils.verifyServerCertificateChain( - targetNodeIds, - certPEMs, - ); - }, crypto: { ops: crypto, }, - reasonToCode: utils.reasonToCode, - codeToReason: utils.codeToReason, + reasonToCode: nodesUtils.reasonToCode, + codeToReason: nodesUtils.codeToReason, logger: logger.getChild(QUICClient.name), }, ctx, ); const quicConnection = quicClient.connection; - // Setting up stream handling - const handleConnectionStream = ( - streamEvent: quicEvents.QUICConnectionStreamEvent, - ) => { - const stream = streamEvent.detail; - handleStream(stream); - }; - quicConnection.addEventListener('connectionStream', handleConnectionStream); + // FIXME: right now I'm not sure it's possible for streams to be emitted while setting up here. + // If we get any while setting up they need to be re-emitted after set up. Otherwise cleaned up. + const throwFunction = () => + never('We should never get connections before setting up handling'); quicConnection.addEventListener( - 'connectionStop', - () => { - quicConnection.removeEventListener( - 'connectionStream', - handleConnectionStream, - ); - }, - { once: true }, + quicEvents.EventQUICConnectionStream.name, + throwFunction, ); const rpcClient = await RPCClient.createRPCClient({ manifest, middlewareFactory: rpcUtils.defaultClientMiddlewareWrapper(), - streamFactory: () => { - return quicConnection.streamNew(); + streamFactory: async () => { + return quicConnection.newStream(); }, logger: logger.getChild(RPCClient.name), }); @@ -184,22 +279,18 @@ class NodeConnection extends EventTarget { // This may de different from the NodeId we validated it as if it renewed at some point. const connection = quicClient.connection; // Remote certificate information should always be available here due to custom verification - const certChain = connection.getRemoteCertsChain().map((pem) => { - const cert = keysUtils.certFromPEM(pem as CertificatePEM); - if (cert == null) never(); - return cert; - }); - if (certChain == null) never(); - const nodeId = keysUtils.certNodeId(certChain[0]); - if (nodeId == null) never(); + const { nodeId, certChain } = nodesUtils.parseRemoteCertsChain( + connection.getRemoteCertsChain(), + ); + const newLogger = logger.getParent() ?? new Logger(this.name); const nodeConnection = new this({ validatedNodeId, nodeId, host: targetHost, port: targetPort, - localHost: connection.localHost as Host, - localPort: connection.localPort as Port, + localHost: connection.localHost as unknown as Host, + localPort: connection.localPort as unknown as Port, certChain, hostname: targetHostname, quicClient, @@ -211,27 +302,46 @@ class NodeConnection extends EventTarget { }:${quicConnection.remotePort}]`, ), }); + // TODO: remove this later based on testing + quicConnection.removeEventListener( + quicEvents.EventQUICConnectionStream.name, + throwFunction, + ); + // All events are handled via the `QUICClient`, any underlying `QUICStream` + // or `QUICConnection` events are expected to be emitted through the + // `QUICClient` + nodeConnection.addEventListener( + nodesEvents.EventNodeConnectionError.name, + nodeConnection.handleEventNodeConnectionError, + ); + nodeConnection.addEventListener( + nodesEvents.EventNodeConnectionClose.name, + nodeConnection.handleEventNodeConnectionClose, + ); quicClient.addEventListener( - 'clientDestroy', - async () => { - // Trigger the nodeConnection destroying - await nodeConnection.destroy({ force: false }); - }, - { once: true }, + quicEvents.EventQUICConnectionStream.name, + nodeConnection.handleEventQUICConnectionStream, + ); + quicClient.addEventListener( + quicEvents.EventQUICClientError.name, + nodeConnection.handleEventQUICError, ); + quicClient.addEventListener( + quicEvents.EventQUICClientDestroyed.name, + nodeConnection.handleEventQUICClientDestroyed, + ); + quicClient.addEventListener(EventAll.name, nodeConnection.handleEventAll); logger.info(`Created ${this.name}`); return nodeConnection; } static async createNodeConnectionReverse({ - handleStream, certChain, nodeId, quicConnection, manifest, logger = new Logger(this.name), }: { - handleStream: (stream: RPCStream) => void; certChain: Array; nodeId: NodeId; quicConnection: QUICConnection; @@ -243,37 +353,19 @@ class NodeConnection extends EventTarget { const rpcClient = await RPCClient.createRPCClient({ manifest, middlewareFactory: rpcUtils.defaultClientMiddlewareWrapper(), - streamFactory: () => { - return quicConnection.streamNew(); + streamFactory: async (_ctx) => { + return quicConnection.newStream(); }, logger: logger.getChild(RPCClient.name), }); - // Setting up stream handling - const handleConnectionStream = ( - streamEvent: quicEvents.QUICConnectionStreamEvent, - ) => { - const stream = streamEvent.detail; - handleStream(stream); - }; - quicConnection.addEventListener('connectionStream', handleConnectionStream); - quicConnection.addEventListener( - 'connectionStop', - () => { - quicConnection.removeEventListener( - 'connectionStream', - handleConnectionStream, - ); - }, - { once: true }, - ); // Creating NodeConnection const nodeConnection = new this({ validatedNodeId: nodeId, nodeId: nodeId, - localHost: quicConnection.localHost as Host, - localPort: quicConnection.localPort as Port, - host: quicConnection.remoteHost as Host, - port: quicConnection.remotePort as Port, + localHost: quicConnection.localHost as unknown as Host, + localPort: quicConnection.localPort as unknown as Port, + host: quicConnection.remoteHost as unknown as Host, + port: quicConnection.remotePort as unknown as Port, certChain, // Hostname and client are not available on reverse connections hostname: undefined, @@ -282,13 +374,31 @@ class NodeConnection extends EventTarget { rpcClient, logger, }); + // All events are handled via the `QUICConnection`, any underlying + // `QUICStream` events are expected to be emitted through the `QUICConnection`. + nodeConnection.addEventListener( + nodesEvents.EventNodeConnectionError.name, + nodeConnection.handleEventNodeConnectionError, + ); + nodeConnection.addEventListener( + nodesEvents.EventNodeConnectionClose.name, + nodeConnection.handleEventNodeConnectionClose, + ); quicConnection.addEventListener( - 'connectionStop', - async () => { - // Trigger the nodeConnection destroying - await nodeConnection.destroy({ force: false }); - }, - { once: true }, + quicEvents.EventQUICConnectionStream.name, + nodeConnection.handleEventQUICConnectionStream, + ); + quicConnection.addEventListener( + quicEvents.EventQUICConnectionError.name, + nodeConnection.handleEventQUICError, + ); + quicConnection.addEventListener( + quicEvents.EventQUICConnectionStopped.name, + nodeConnection.handleEventQUICConnectionStopped, + ); + quicConnection.addEventListener( + EventAll.name, + nodeConnection.handleEventAll, ); logger.info(`Created ${this.name}`); return nodeConnection; @@ -321,7 +431,6 @@ class NodeConnection extends EventTarget { rpcClient: RPCClient; logger: Logger; }) { - super(); this.validatedNodeId = validatedNodeId; this.nodeId = nodeId; this.host = host; @@ -347,16 +456,49 @@ class NodeConnection extends EventTarget { await this.quicConnection.stop( force ? { - applicationError: true, - errorCode: 0, - errorMessage: 'NodeConnection is forcing destruction', + isApp: true, + errorCode: 1, + reason: Buffer.from('NodeConnection is forcing destruction'), force: true, } : {}, ); await this.rpcClient.destroy(); this.logger.debug(`${this.constructor.name} triggered destroyed event`); - this.dispatchEvent(new nodesEvents.NodeConnectionDestroyEvent()); + // Removing all event listeners + this.addEventListener( + nodesEvents.EventNodeConnectionError.name, + this.handleEventNodeConnectionError, + ); + this.addEventListener( + nodesEvents.EventNodeConnectionClose.name, + this.handleEventNodeConnectionClose, + ); + // If the client exists then it was all registered to that, + // otherwise the connection + const quicClientOrConnection = this.quicClient ?? this.quicConnection; + quicClientOrConnection.addEventListener( + quicEvents.EventQUICConnectionStream.name, + this.handleEventQUICConnectionStream, + ); + quicClientOrConnection.addEventListener( + quicEvents.EventQUICConnectionError.name, + this.handleEventQUICError, + ); + quicClientOrConnection.addEventListener( + quicEvents.EventQUICClientDestroyed.name, + this.handleEventQUICClientDestroyed, + ); + quicClientOrConnection.addEventListener( + quicEvents.EventQUICConnectionStopped.name, + this.handleEventQUICConnectionStopped, + ); + quicClientOrConnection.addEventListener( + quicEvents.EventQUICClientError.name, + this.handleEventQUICError, + ); + quicClientOrConnection.addEventListener(EventAll.name, this.handleEventAll); + this.dispatchEvent(new nodesEvents.EventNodeConnectionDestroy()); this.logger.info(`Destroyed ${this.constructor.name}`); } diff --git a/src/nodes/NodeConnectionManager.ts b/src/nodes/NodeConnectionManager.ts index 13c0f629b..9ea69f8ab 100644 --- a/src/nodes/NodeConnectionManager.ts +++ b/src/nodes/NodeConnectionManager.ts @@ -1,9 +1,8 @@ -import type { QUICConnection, QUICSocket } from '@matrixai/quic'; +import type { LockRequest } from '@matrixai/async-locks'; import type { ResourceAcquire } from '@matrixai/resources'; -import type { ContextTimed } from '@matrixai/contexts'; -import type { CertificatePEM } from '../keys/types'; -import type KeyRing from '../keys/KeyRing'; -import type { Host, Hostname, Port } from '../network/types'; +import type { ContextTimedInput, ContextTimed } from '@matrixai/contexts'; +import type { PromiseCancellable } from '@matrixai/async-cancellable'; +import type { ClientCryptoOps, QUICConnection } from '@matrixai/quic'; import type NodeGraph from './NodeGraph'; import type { NodeAddress, @@ -11,88 +10,139 @@ import type { NodeId, NodeIdString, SeedNodes, + NodesOptions, } from './types'; -import type NodeManager from './NodeManager'; -import type { LockRequest } from '@matrixai/async-locks/dist/types'; -import type { HolePunchRelayMessage } from '../agent/handlers/types'; -import type { ClientCrypto } from '@matrixai/quic'; -import type { ContextTimedInput } from '@matrixai/contexts/dist/types'; -import type { RPCStream } from '../rpc/types'; -import type { TLSConfig } from '../network/types'; -import type { ServerCrypto, events as QuicEvents } from '@matrixai/quic'; -import type { PromiseCancellable } from '@matrixai/async-cancellable'; -import { withF } from '@matrixai/resources'; +import type KeyRing from '../keys/KeyRing'; +import type { Key, CertificatePEM } from '../keys/types'; +import type { + ConnectionData, + Host, + Hostname, + Port, + TLSConfig, +} from '../network/types'; +import type { ServerManifest } from '../rpc/types'; +import type { HolePunchRelayMessage } from './agent/types'; import Logger from '@matrixai/logger'; +import { withF } from '@matrixai/resources'; import { ready, StartStop } from '@matrixai/async-init/dist/StartStop'; import { IdInternal } from '@matrixai/id'; import { Lock, LockBox } from '@matrixai/async-locks'; import { Timer } from '@matrixai/timer'; import { timedCancellable, context } from '@matrixai/contexts/dist/decorators'; -import { QUICServer } from '@matrixai/quic'; +import { AbstractEvent, EventAll } from '@matrixai/events'; +import { + QUICSocket, + QUICServer, + events as quicEvents, + utils as quicUtils, +} from '@matrixai/quic'; +import { running, status } from '@matrixai/async-init'; import NodeConnection from './NodeConnection'; import * as nodesUtils from './utils'; import * as nodesErrors from './errors'; +import * as nodesEvents from './events'; +import manifestClientAgent from './agent/callers'; +import * as keysUtils from '../keys/utils'; import * as validationUtils from '../validation/utils'; import * as networkUtils from '../network/utils'; -import { never } from '../utils'; import * as utils from '../utils'; -import { clientManifest as agentClientManifest } from '../agent/handlers/clientManifest'; -import * as keysUtils from '../keys/utils'; +import config from '../config'; +import RPCServer from '../rpc/RPCServer'; +import * as rpcUtilsMiddleware from '../rpc/utils/middleware'; -// TODO: check all locking and add cancellation for it. - -type AgentClientManifest = typeof agentClientManifest; +type ManifestClientAgent = typeof manifestClientAgent; type ConnectionAndTimer = { - connection: NodeConnection; + connection: NodeConnection; timer: Timer | null; usageCount: number; }; +/** + * NodeConnectionManager is a server that manages all node connections. + * It manages both initiated and received connections + * + * It's an event target that emits events for new connections. + * + * We will need to fully encapsulate all the errors in NCM if we can. + * Otherwise, it goes all the way to PolykeyAgent. + * + * That means the QUICSocket, QUICServer and QUICClient + * As well as the QUICConnection and QUICStream. + * The NCM basically encapsulates it. + * + * The NCM and NC both must encapsulate all the QUIC transport. + * + * Events: + * + * - connectionManagerStop + * - connectionManagerError + * - nodeConnection + * - nodeConnectionError + * - nodeConnectionStream + * - nodeConnectionDestroy + */ interface NodeConnectionManager extends StartStop {} -@StartStop() +@StartStop({ + eventStart: nodesEvents.EventNodeConnectionManagerStart, + eventStarted: nodesEvents.EventNodeConnectionManagerStarted, + eventStop: nodesEvents.EventNodeConnectionManagerStop, + eventStopped: nodesEvents.EventNodeConnectionManagerStopped, +}) class NodeConnectionManager { /** - * Time used to establish `NodeConnection` + * Alpha constant for kademlia + * The number of the closest nodes to contact initially */ - public readonly connectionConnectTime: number; + public readonly connectionFindConcurrencyLimit: number; /** - * Time to live for `NodeConnection` + * Time to wait to garbage collect un-used node connections. */ - public readonly connectionTimeoutTime: number; + public readonly connectionIdleTimeoutTime: number; /** - * Default timeout for pinging nodes + * Time used to establish `NodeConnection` */ - public readonly pingTimeoutTime: number; + public readonly connectionConnectTimeoutTime: number; /** - * Alpha constant for kademlia - * The number of the closest nodes to contact initially + * Time to keep alive node connection. */ - public readonly initialClosestNodes: number; + public readonly connectionKeepAliveTimeoutTime: number; /** - * Default timeout for reverse hole punching. + * Time interval for sending keep alive messages. */ - public readonly connectionHolePunchTimeoutTime: number; + public readonly connectionKeepAliveIntervalTime: number; /** * Initial delay between punch packets, delay doubles each attempt. */ public readonly connectionHolePunchIntervalTime: number; - protected handleStream: (stream: RPCStream) => void = - () => never() as (stream: RPCStream) => void; + /** + * Max parse buffer size before RPC parser throws an parse error. + */ + public readonly rpcParserBufferSize: number; + + /** + * Default timeout for RPC handlers + */ + public readonly rpcCallTimeoutTime: number; + protected logger: Logger; - protected nodeGraph: NodeGraph; protected keyRing: KeyRing; + protected nodeGraph: NodeGraph; + protected tlsConfig: TLSConfig; + protected seedNodes: SeedNodes; + protected quicSocket: QUICSocket; protected quicServer: QUICServer; - // NodeManager has to be passed in during start to allow co-dependency - protected nodeManager: NodeManager | undefined; - protected seedNodes: SeedNodes; + + protected quicClientCrypto: ClientCryptoOps; + /** * Data structure to store all NodeConnections. If a connection to a node n does * not exist, no entry for n will exist in the map. Alternatively, if a @@ -104,138 +154,349 @@ class NodeConnectionManager { * NodeIds can't be used to properly retrieve a value from the map. */ protected connections: Map = new Map(); + protected connectionLocks: LockBox = new LockBox(); - // Tracks the backoff period for offline nodes - protected nodesBackoffMap: Map< - string, - { lastAttempt: number; delay: number } - > = new Map(); - protected backoffDefault: number = 1000 * 60 * 5; // 5 min - protected backoffMultiplier: number = 2; // Doubles every failure - protected tlsConfig: TLSConfig; - protected connectionKeepAliveIntervalTime: number; - protected connectionMaxIdleTimeout: number; - protected crypto: ServerCrypto & ClientCrypto; - protected serverConnectionHandler = async ( - connectionEvent: QuicEvents.QUICServerConnectionEvent, + + protected rpcServer?: RPCServer; + + /** + * Dispatches a `EventNodeConnectionManagerClose` in response to any `NodeConnectionManager` + * error event. Will trigger stop of the `NodeConnectionManager` via the + * `EventNodeConnectionManagerError` -> `EventNodeConnectionManagerClose` event path. + */ + protected handleEventNodeConnectionManagerError = ( + evt: nodesEvents.EventNodeConnectionManagerError, + ) => { + this.logger.warn( + `NodeConnectionManager error caused by ${evt.detail.message}`, + ); + this.dispatchEvent(new nodesEvents.EventNodeConnectionClose()); + }; + + /** + * Triggers the destruction of the `NodeConnectionManager`. Since this is only in + * response to an underlying problem or close it will force destroy. + * Dispatched by the `EventNodeConnectionManagerError` event as the + * `EventNodeConnectionManagerError` -> `EventNodeConnectionManagerClose` event path. + */ + protected handleEventNodeConnectionManagerClose = async ( + _evt: nodesEvents.EventNodeConnectionManagerClose, ) => { - const quicConnection = connectionEvent.detail; - await this.handleConnectionReverse(quicConnection); + this.logger.warn(`close event triggering NodeConnectionManager.stop`); + if (this[running] && this[status] !== 'stopping') { + await this.stop(); + } + }; + + protected handleEventNodeConnectionStream = async ( + e: nodesEvents.EventNodeConnectionStream, + ) => { + const stream = e.detail; + this.rpcServer!.handleStream(stream); + }; + + /** + * Redispatches `QUICSOcket` or `QUICServer` error events as `NodeConnectionManager` error events. + * This should trigger the destruction of the `NodeConnection` through the + * `EventNodeConnectionError` -> `EventNodeConnectionClose` event path. + */ + protected handleEventQUICError = (evt: quicEvents.EventQUICSocketError) => { + const err = new nodesErrors.ErrorNodeConnectionManagerInternalError( + undefined, + { cause: evt.detail }, + ); + this.dispatchEvent( + new nodesEvents.EventNodeConnectionManagerError({ detail: err }), + ); + }; + + /** + * Handle unexpected stoppage of the QUICSocket. Not expected to happen + * without error but we have it just in case. + */ + protected handleEventQUICSocketStopped = ( + _evt: quicEvents.EventQUICSocketStopped, + ) => { + const err = new nodesErrors.ErrorNodeConnectionManagerInternalError( + 'QUICSocket stopped unexpectedly', + ); + this.dispatchEvent( + new nodesEvents.EventNodeConnectionManagerError({ detail: err }), + ); + }; + + /** + * Handle unexpected stoppage of the QUICServer. Not expected to happen + * without error but we have it just in case. + */ + protected handleEventQUICServerStopped = ( + _evt: quicEvents.EventQUICServerStopped, + ) => { + const err = new nodesErrors.ErrorNodeConnectionManagerInternalError( + 'QUICServer stopped unexpectedly', + ); + this.dispatchEvent( + new nodesEvents.EventNodeConnectionManagerError({ detail: err }), + ); + }; + + /** + * Handles `EventQUICServerConnection` events. These are reverser or server + * peer initated connections that needs to be handled and added to the + * connectio map. + */ + protected handleEventQUICServerConnection = async ( + evt: quicEvents.EventQUICServerConnection, + ) => { + await this.handleConnectionReverse(evt.detail); + }; + + /** + * Handles all events and redispatches them upwards + */ + protected handleEventAll = (evt: EventAll) => { + const event = evt.detail; + if (event instanceof AbstractEvent) { + this.dispatchEvent(event.clone()); + } }; public constructor({ keyRing, nodeGraph, - quicSocket, - crypto, tlsConfig, seedNodes = {}, - initialClosestNodes = 3, - connectionConnectTime = 2_000, - connectionTimeoutTime = 60_000, - pingTimeoutTime = 2_000, - connectionHolePunchTimeoutTime = 4_000, - connectionHolePunchIntervalTime = 250, - connectionKeepAliveIntervalTime = 10_000, - connectionMaxIdleTimeout = 60_000, + options = {}, logger, }: { keyRing: KeyRing; nodeGraph: NodeGraph; - quicSocket: QUICSocket; - crypto: ServerCrypto & ClientCrypto; tlsConfig: TLSConfig; seedNodes?: SeedNodes; - initialClosestNodes?: number; - connectionConnectTime?: number; - connectionTimeoutTime?: number; - pingTimeoutTime?: number; - connectionHolePunchTimeoutTime?: number; - connectionHolePunchIntervalTime?: number; - connectionKeepAliveIntervalTime?: number; - connectionMaxIdleTimeout?: number; - logger: Logger; + options?: Partial; + logger?: Logger; }) { - this.logger = logger ?? new Logger(NodeConnectionManager.name); + const optionsDefaulted = utils.mergeObjects(options, { + connectionFindConcurrencyLimit: + config.defaultsSystem.nodesConnectionFindConcurrencyLimit, + connectionIdleTimeoutTime: + config.defaultsSystem.nodesConnectionIdleTimeoutTime, + connectionConnectTimeoutTime: + config.defaultsSystem.clientConnectTimeoutTime, + connectionKeepAliveTimeoutTime: + config.defaultsSystem.clientKeepAliveTimeoutTime, + connectionKeepAliveIntervalTime: + config.defaultsSystem.clientKeepAliveIntervalTime, + connectionHolePunchIntervalTime: + config.defaultsSystem.nodesConnectionHolePunchIntervalTime, + rpcParserBufferSize: config.defaultsSystem.rpcParserBufferSize, + rpcCallTimeoutTime: config.defaultsSystem.rpcCallTimeoutTime, + }); + this.logger = logger ?? new Logger(this.constructor.name); this.keyRing = keyRing; this.nodeGraph = nodeGraph; - this.quicSocket = quicSocket; this.tlsConfig = tlsConfig; - this.crypto = crypto; - const localNodeIdEncoded = nodesUtils.encodeNodeId(keyRing.getNodeId()); - delete seedNodes[localNodeIdEncoded]; - this.seedNodes = seedNodes; - this.initialClosestNodes = initialClosestNodes; - this.connectionConnectTime = connectionConnectTime; - this.connectionTimeoutTime = connectionTimeoutTime; - this.connectionHolePunchTimeoutTime = connectionHolePunchTimeoutTime; - this.connectionHolePunchIntervalTime = connectionHolePunchIntervalTime; - this.pingTimeoutTime = pingTimeoutTime; - this.connectionKeepAliveIntervalTime = connectionKeepAliveIntervalTime; - this.connectionMaxIdleTimeout = connectionMaxIdleTimeout; - // Setting up QUICServer - const resolveHostname = (host) => { - return networkUtils.resolveHostname(host)[0] ?? ''; + // Filter out own node ID + const nodeIdEncodedOwn = nodesUtils.encodeNodeId(keyRing.getNodeId()); + this.seedNodes = utils.filterObject(seedNodes, ([k]) => { + return k !== nodeIdEncodedOwn; + }) as SeedNodes; + this.connectionFindConcurrencyLimit = + optionsDefaulted.connectionFindConcurrencyLimit; + this.connectionIdleTimeoutTime = optionsDefaulted.connectionIdleTimeoutTime; + this.connectionConnectTimeoutTime = + optionsDefaulted.connectionConnectTimeoutTime; + this.connectionKeepAliveTimeoutTime = + optionsDefaulted.connectionKeepAliveTimeoutTime; + this.connectionKeepAliveIntervalTime = + optionsDefaulted.connectionKeepAliveIntervalTime; + this.connectionHolePunchIntervalTime = + optionsDefaulted.connectionHolePunchIntervalTime; + this.rpcParserBufferSize = optionsDefaulted.rpcParserBufferSize; + this.rpcCallTimeoutTime = optionsDefaulted.rpcCallTimeoutTime; + // Note that all buffers allocated for crypto operations is using + // `allocUnsafeSlow`. Which ensures that the underlying `ArrayBuffer` + // is not shared. Also, all node buffers satisfy the `ArrayBuffer` interface. + const quicClientCrypto = { + async randomBytes(data: ArrayBuffer): Promise { + const randomBytes = keysUtils.getRandomBytes(data.byteLength); + randomBytes.copy(utils.bufferWrap(data)); + }, + }; + const quicServerCrypto = { + key: keysUtils.generateKey(), + ops: { + async sign(key: ArrayBuffer, data: ArrayBuffer): Promise { + const sig = keysUtils.macWithKey( + utils.bufferWrap(key) as Key, + utils.bufferWrap(data), + ); + // Convert the MAC to an ArrayBuffer + return sig.slice().buffer; + }, + async verify( + key: ArrayBuffer, + data: ArrayBuffer, + sig: ArrayBuffer, + ): Promise { + return keysUtils.authWithKey( + utils.bufferWrap(key) as Key, + utils.bufferWrap(data), + utils.bufferWrap(sig), + ); + }, + }, }; - this.quicServer = new QUICServer({ + const quicSocket = new QUICSocket({ + logger: this.logger.getChild(QUICSocket.name), + }); + // By the time we get to using QUIC server, all hostnames would have been + // resolved, we would not resolve hostnames inside the QUIC server. + // This is because node connections require special hostname resolution + // procedures. + const quicServer = new QUICServer({ config: { - keepAliveIntervalTime: connectionKeepAliveIntervalTime, - maxIdleTimeout: connectionMaxIdleTimeout, + maxIdleTimeout: optionsDefaulted.connectionKeepAliveTimeoutTime, + keepAliveIntervalTime: optionsDefaulted.connectionKeepAliveIntervalTime, key: tlsConfig.keyPrivatePem, cert: tlsConfig.certChainPem, verifyPeer: true, - verifyAllowFail: true, - }, - crypto: { - key: keysUtils.generateKey(), - ops: crypto, + verifyCallback: networkUtils.verifyClientCertificateChain, }, - verifyCallback: networkUtils.verifyClientCertificateChain, - logger: logger.getChild(QUICServer.name + 'Agent'), + crypto: quicServerCrypto, socket: quicSocket, - resolveHostname, - reasonToCode: utils.reasonToCode, - codeToReason: utils.codeToReason, + reasonToCode: nodesUtils.reasonToCode, + codeToReason: nodesUtils.codeToReason, + minIdleTimeout: optionsDefaulted.connectionConnectTimeoutTime, + logger: this.logger.getChild(QUICServer.name), }); + this.quicClientCrypto = quicClientCrypto; + this.quicSocket = quicSocket; + this.quicServer = quicServer; + } + + /** + * Get the host that node connection manager is bound to. + */ + @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) + public get host(): Host { + return this.quicSocket.host as unknown as Host; + } + + /** + * Get the port that node connection manager is bound to. + */ + @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) + public get port(): Port { + return this.quicSocket.port as unknown as Port; } public async start({ - nodeManager, - handleStream, + host = '::' as Host, + port = 0 as Port, + reuseAddr = false, + ipv6Only = false, + manifest = {}, }: { - nodeManager: NodeManager; - handleStream: (stream: RPCStream) => void; + host?: Host; + port?: Port; + reuseAddr?: boolean; + ipv6Only?: boolean; + manifest?: ServerManifest; }) { - this.logger.info(`Starting ${this.constructor.name}`); - this.nodeManager = nodeManager; - // Adding seed nodes - for (const nodeIdEncoded in this.seedNodes) { - const nodeId = nodesUtils.decodeNodeId(nodeIdEncoded); - if (nodeId == null) never(); - await this.nodeManager.setNode( - nodeId, - this.seedNodes[nodeIdEncoded], - true, - ); - } - this.handleStream = handleStream; - // Starting QUICServer - // No host or port is provided here, it's configured in the shared QUICSocket. - await this.quicServer.start(); + const address = networkUtils.buildAddress(host, port); + this.logger.info(`Start ${this.constructor.name} on ${address}`); + + // We should expect that seed nodes are already in the node manager + // It should not be managed here! + + // setting up RPCServer + this.rpcServer = await RPCServer.createRPCServer({ + manifest, + middlewareFactory: rpcUtilsMiddleware.defaultServerMiddlewareWrapper( + undefined, + this.rpcParserBufferSize, + ), + sensitive: true, + handlerTimeoutTime: this.rpcCallTimeoutTime, + handlerTimeoutGraceTime: this.rpcCallTimeoutTime + 2000, + logger: this.logger.getChild(RPCServer.name), + }); + + // Setting up QUICSocket + await this.quicSocket.start({ + host, + port, + reuseAddr, + ipv6Only, + }); + this.quicSocket.addEventListener( + quicEvents.EventQUICSocketError.name, + this.handleEventQUICError, + ); + this.quicSocket.addEventListener( + quicEvents.EventQUICSocketStopped.name, + this.handleEventQUICSocketStopped, + ); + this.quicSocket.addEventListener(EventAll.name, this.handleEventAll); + + // QUICServer will simply re-use the shared `QUICSocket` + await this.quicServer.start({ + host, + port, + reuseAddr, + ipv6Only, + }); this.quicServer.addEventListener( - 'serverConnection', - this.serverConnectionHandler, + quicEvents.EventQUICServerError.name, + this.handleEventQUICError, ); + this.quicServer.addEventListener( + quicEvents.EventQUICServerStopped.name, + this.handleEventQUICServerStopped, + ); + this.quicServer.addEventListener( + quicEvents.EventQUICServerConnection.name, + this.handleEventQUICServerConnection, + ); + this.quicSocket.addEventListener(EventAll.name, this.handleEventAll); this.logger.info(`Started ${this.constructor.name}`); } public async stop() { - this.logger.info(`Stopping ${this.constructor.name}`); + this.logger.info(`Stop ${this.constructor.name}`); + + this.removeEventListener( + nodesEvents.EventNodeConnectionManagerError.name, + this.handleEventNodeConnectionManagerError, + ); + this.removeEventListener( + nodesEvents.EventNodeConnectionManagerClose.name, + this.handleEventNodeConnectionManagerClose, + ); + this.quicSocket.removeEventListener( + quicEvents.EventQUICSocketError.name, + this.handleEventQUICError, + ); + this.quicSocket.removeEventListener( + quicEvents.EventQUICSocketStopped.name, + this.handleEventQUICSocketStopped, + ); + this.quicSocket.removeEventListener(EventAll.name, this.handleEventAll); + this.quicServer.removeEventListener( + quicEvents.EventQUICServerError.name, + this.handleEventQUICError, + ); this.quicServer.removeEventListener( - 'serverConnection', - this.serverConnectionHandler, + quicEvents.EventQUICServerStopped.name, + this.handleEventQUICServerStopped, ); - this.nodeManager = undefined; + this.quicServer.removeEventListener( + quicEvents.EventQUICServerConnection.name, + this.handleEventQUICServerConnection, + ); + this.quicSocket.removeEventListener(EventAll.name, this.handleEventAll); + const destroyProms: Array> = []; for (const [nodeId, connAndTimer] of this.connections) { if (connAndTimer.connection == null) continue; @@ -247,7 +508,11 @@ class NodeConnectionManager { } await Promise.all(destroyProms); await this.quicServer.stop({ force: true }); - this.handleStream = () => never(); + await this.quicSocket.stop({ force: true }); + await this.rpcServer?.destroy({ + force: true, + reason: new nodesErrors.ErrorNodeConnectionManagerStopping(), + }); this.logger.info(`Stopped ${this.constructor.name}`); } @@ -264,7 +529,7 @@ class NodeConnectionManager { public async acquireConnection( targetNodeId: NodeId, ctx?: Partial, - ): Promise>> { + ): Promise>> { if (this.keyRing.getNodeId().equals(targetNodeId)) { this.logger.warn('Attempting connection to our own NodeId'); } @@ -301,7 +566,7 @@ class NodeConnectionManager { ); connectionAndTimer.timer = new Timer({ handler: async () => await this.destroyConnection(targetNodeId), - delay: this.connectionTimeoutTime, + delay: this.connectionIdleTimeoutTime, }); } }, @@ -321,18 +586,18 @@ class NodeConnectionManager { */ public withConnF( targetNodeId: NodeId, - f: (conn: NodeConnection) => Promise, + f: (conn: NodeConnection) => Promise, ctx?: Partial, ): PromiseCancellable; @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) @timedCancellable( true, (nodeConnectionManager: NodeConnectionManager) => - nodeConnectionManager.connectionConnectTime, + nodeConnectionManager.connectionConnectTimeoutTime, ) public async withConnF( targetNodeId: NodeId, - f: (conn: NodeConnection) => Promise, + f: (conn: NodeConnection) => Promise, @context ctx: ContextTimed, ): Promise { return await withF( @@ -356,7 +621,7 @@ class NodeConnectionManager { public async *withConnG( targetNodeId: NodeId, g: ( - conn: NodeConnection, + conn: NodeConnection, ) => AsyncGenerator, ctx?: Partial, ): AsyncGenerator { @@ -364,7 +629,7 @@ class NodeConnectionManager { const [release, conn] = await acquire(); let caughtError; try { - if (conn == null) never(); + if (conn == null) utils.never(); return yield* g(conn); } catch (e) { caughtError = e; @@ -387,7 +652,7 @@ class NodeConnectionManager { @timedCancellable( true, (nodeConnectionManager: NodeConnectionManager) => - nodeConnectionManager.connectionConnectTime, + nodeConnectionManager.connectionConnectTimeoutTime, ) protected async getConnection( targetNodeId: NodeId, @@ -401,11 +666,11 @@ class NodeConnectionManager { // If there was no address provided then we need to find it. if (address == null) { // Find the node - address = await this.findNode(targetNodeId, undefined, undefined, ctx); + address = await this.findNode(targetNodeId, undefined, ctx); if (address == null) throw new nodesErrors.ErrorNodeGraphNodeIdNotFound(); } // Then we just get the connection, it should already exist. - return await this.getConnectionWithAddress(targetNodeId, address, ctx); + return this.getConnectionWithAddress(targetNodeId, address, ctx); } protected async getExistingConnection( @@ -448,7 +713,7 @@ class NodeConnectionManager { @timedCancellable( true, (nodeConnectionManager: NodeConnectionManager) => - nodeConnectionManager.connectionConnectTime, + nodeConnectionManager.connectionConnectTimeoutTime, ) protected async getConnectionWithAddress( targetNodeId: NodeId, @@ -474,7 +739,7 @@ class NodeConnectionManager { return connAndTimer; } // Should throw before reaching here - never(); + utils.never(); }) .finally(() => { this.logger.debug(`lock finished for ${targetNodeIdEncoded}`); @@ -496,7 +761,9 @@ class NodeConnectionManager { ): Promise> { const nodesEncoded = nodeIds.map((v) => nodesUtils.encodeNodeId(v)); this.logger.debug(`getting multi-connection for ${nodesEncoded}`); - if (nodeIds.length === 0) throw Error('TMP, must provide at least 1 node'); + if (nodeIds.length === 0) { + throw new nodesErrors.ErrorNodeConnectionManagerNodeIdRequired(); + } const connectionsResults: Map = new Map(); // 1. short circuit any existing connections const nodesShortlist: Set = new Set(); @@ -572,9 +839,12 @@ class NodeConnectionManager { await Promise.allSettled(connProms); } if (connectionsResults.size === 0) { - // TODO: This needs to throw if none were established. - // The usual use case is a single node, this shouldn't be a aggregate error type. - throw Error('No connections established!'); + throw new nodesErrors.ErrorNodeConnectionManagerMultiConnectionFailed( + undefined, + { + cause: new AggregateError(await Promise.allSettled(connProms)), + }, + ); } return connectionsResults; } @@ -604,17 +874,16 @@ class NodeConnectionManager { ); const iceProm = this.initiateHolePunch(nodeIds, ctx); const connection = - await NodeConnection.createNodeConnection( + await NodeConnection.createNodeConnection( { - handleStream: this.handleStream, targetNodeIds: nodeIds, - manifest: agentClientManifest, - crypto: this.crypto, + manifest: manifestClientAgent, + crypto: this.quicClientCrypto, targetHost: address.host, targetPort: address.port, tlsConfig: this.tlsConfig, connectionKeepAliveIntervalTime: this.connectionKeepAliveIntervalTime, - connectionMaxIdleTimeout: this.connectionMaxIdleTimeout, + connectionKeepAliveTimeoutTime: this.connectionKeepAliveTimeoutTime, quicSocket: this.quicSocket, logger: this.logger.getChild( `${NodeConnection.name} [${address.host}:${address.port}]`, @@ -642,6 +911,8 @@ class NodeConnectionManager { // 3. if already exists then clean up await connection.destroy({ force: true }); // I can only see this happening as a race condition with creating a forward connection and receiving a reverse. + // FIXME: only here to see if this condition happens. + // this NEEDS to be removed, but I want to know if this branch happens at all. throw Error( 'TMP IMP, This should be exceedingly rare, lets see if it happens', ); @@ -650,10 +921,6 @@ class NodeConnectionManager { // Final setup const newConnAndTimer = this.addConnection(nodeId, connection); // We can assume connection was established and destination was valid, we can add the target to the nodeGraph - await this.nodeManager?.setNode(nodeId, { - host: address.host, - port: address.port, - }); connectionsResults.set(nodeIdString, newConnAndTimer); this.logger.debug( `Created NodeConnection for ${nodesUtils.encodeNodeId( @@ -664,19 +931,22 @@ class NodeConnectionManager { /** * This will take a `QUICConnection` emitted by the `QUICServer` and handle adding it to the connection map + * This will also set up some event handling for the connection. */ @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) protected async handleConnectionReverse(quicConnection: QUICConnection) { // Checking NodeId // No specific error here, validation is handled by the QUICServer - const certChain = quicConnection.getRemoteCertsChain().map((pem) => { - const cert = keysUtils.certFromPEM(pem as CertificatePEM); - if (cert == null) never(); + const certChain = quicConnection.getRemoteCertsChain().map((der) => { + const cert = keysUtils.certFromPEM( + quicUtils.derToPEM(der) as CertificatePEM, + ); + if (cert == null) utils.never(); return cert; }); - if (certChain == null) never(); + if (certChain == null) utils.never(); const nodeId = keysUtils.certNodeId(certChain[0]); - if (nodeId == null) never(); + if (nodeId == null) utils.never(); const nodeIdString = nodeId.toString() as NodeIdString; // TODO: A connection can fail while awaiting lock. We should abort early in this case. return await this.connectionLocks.withF( @@ -686,20 +956,19 @@ class NodeConnectionManager { if (this.connections.has(nodeIdString)) { // Reject and return early. await quicConnection.stop({ - applicationError: true, - errorCode: 42, - errorMessage: 'Connection already exists, forcing close', + isApp: true, + errorCode: 42, // TODO: use an actual code + reason: Buffer.from('Connection already exists, forcing close'), force: true, }); return; } const nodeConnection = - await NodeConnection.createNodeConnectionReverse( + await NodeConnection.createNodeConnectionReverse( { - handleStream: this.handleStream, nodeId, certChain, - manifest: agentClientManifest, + manifest: manifestClientAgent, quicConnection: quicConnection, logger: this.logger.getChild( `${NodeConnection.name} [${nodesUtils.encodeNodeId(nodeId)}@${ @@ -710,11 +979,6 @@ class NodeConnectionManager { ); // Final setup this.addConnection(nodeId, nodeConnection); - // We can add the target to the nodeGraph - await this.nodeManager?.setNode(nodeId, { - host: nodeConnection.host, - port: nodeConnection.port, - }); }, ); } @@ -725,31 +989,41 @@ class NodeConnectionManager { */ protected addConnection( nodeId: NodeId, - nodeConnection: NodeConnection, + nodeConnection: NodeConnection, ): ConnectionAndTimer { const nodeIdString = nodeId.toString() as NodeIdString; // Check if exists in map, this should never happen but better safe than sorry. - if (this.connections.has(nodeIdString)) never(); - const handleDestroy = async () => { - this.logger.debug('stream destroyed event'); - // To avoid deadlock only in the case where this is called - // we want to check for destroying connection and read lock - // If the connection is calling destroyCallback then it SHOULD exist in the connection map. - if (!this.connections.has(nodeIdString)) return; - // Already locked so already destroying - if (this.connectionLocks.isLocked(nodeIdString)) return; - await this.destroyConnection(nodeId); - }; - nodeConnection.addEventListener('destroy', handleDestroy, { - once: true, - }); + if (this.connections.has(nodeIdString)) utils.never(); + // Setting up events + nodeConnection.addEventListener( + nodesEvents.EventNodeConnectionStream.name, + this.handleEventNodeConnectionStream, + ); + nodeConnection.addEventListener(EventAll.name, this.handleEventAll); + nodeConnection.addEventListener( + nodesEvents.EventNodeConnectionDestroyed.name, + async () => { + // To avoid deadlock only in the case where this is called + // we want to check for destroying connection and read lock + // If the connection is calling destroyCallback then it SHOULD exist in the connection map. + // Already locked so already destroying + if (this.connectionLocks.isLocked(nodeIdString)) return; + await this.destroyConnection(nodeId); + nodeConnection.removeEventListener( + nodesEvents.EventNodeConnectionStream.name, + this.handleEventNodeConnectionStream, + ); + nodeConnection.removeEventListener(EventAll.name, this.handleEventAll); + }, + { once: true }, + ); // Creating TTL timeout. // We don't create a TTL for seed nodes. const timeToLiveTimer = !this.isSeedNode(nodeId) ? new Timer({ handler: async () => await this.destroyConnection(nodeId), - delay: this.connectionTimeoutTime, + delay: this.connectionIdleTimeoutTime, }) : null; // Add to map @@ -759,6 +1033,16 @@ class NodeConnectionManager { usageCount: 0, }; this.connections.set(nodeIdString, newConnAndTimer); + const connectionData: ConnectionData = { + remoteNodeId: nodeConnection.nodeId, + remoteHost: nodeConnection.host, + remotePort: nodeConnection.port, + }; + this.dispatchEvent( + new nodesEvents.EventNodeConnectionManagerConnection({ + detail: connectionData, + }), + ); return newConnAndTimer; } @@ -806,23 +1090,13 @@ class NodeConnectionManager { @timedCancellable( true, (nodeConnectionManager: NodeConnectionManager) => - nodeConnectionManager.connectionHolePunchTimeoutTime, + nodeConnectionManager.connectionConnectTimeoutTime, ) public async holePunchReverse( host: Host, port: Port, @context ctx: ContextTimed, ): Promise { - const connectionMap = this.quicSocket.connectionMap; - // Checking existing connections - for (const [, connection] of connectionMap.serverConnections) { - const connectionHost = connection.remoteHost; - const connectionPort = connection.remotePort; - if (host === connectionHost && port === connectionPort) { - // Connection exists, return early - return; - } - } // We need to send a random data packet to the target until the process times out or a connection is established let ended = false; const endedProm = utils.promise(); @@ -856,13 +1130,11 @@ class NodeConnectionManager { * Will attempt to find a connection via a Kademlia search. * The connection will be established in the process. * @param targetNodeId Id of the node we are tying to find - * @param ignoreRecentOffline skips nodes that are within their backoff period * @param pingTimeoutTime timeout for any ping attempts * @param ctx */ public findNode( targetNodeId: NodeId, - ignoreRecentOffline?: boolean, pingTimeoutTime?: number, ctx?: Partial, ): PromiseCancellable; @@ -870,7 +1142,6 @@ class NodeConnectionManager { @timedCancellable(true) public async findNode( targetNodeId: NodeId, - ignoreRecentOffline: boolean = false, pingTimeoutTime: number | undefined, @context ctx: ContextTimed, ): Promise { @@ -892,8 +1163,7 @@ class NodeConnectionManager { // Otherwise, attempt to locate it by contacting network address = await this.getClosestGlobalNodes( targetNodeId, - ignoreRecentOffline, - pingTimeoutTime ?? this.pingTimeoutTime, + pingTimeoutTime ?? this.connectionConnectTimeoutTime, ctx, ); if (address != null) { @@ -920,14 +1190,12 @@ class NodeConnectionManager { * port). * @param targetNodeId ID of the node attempting to be found (i.e. attempting * to find its IP address and port) - * @param ignoreRecentOffline skips nodes that are within their backoff period * @param pingTimeoutTime * @param ctx * @returns whether the target node was located in the process */ public getClosestGlobalNodes( targetNodeId: NodeId, - ignoreRecentOffline?: boolean, pingTimeoutTime?: number, ctx?: Partial, ): PromiseCancellable; @@ -935,7 +1203,6 @@ class NodeConnectionManager { @timedCancellable(true) public async getClosestGlobalNodes( targetNodeId: NodeId, - ignoreRecentOffline: boolean = false, pingTimeoutTime: number | undefined, @context ctx: ContextTimed, ): Promise { @@ -945,7 +1212,7 @@ class NodeConnectionManager { // Get the closest alpha nodes to the target node (set as shortlist) const shortlist = await this.nodeGraph.getClosestNodes( targetNodeId, - this.initialClosestNodes, + this.connectionFindConcurrencyLimit, ); // If we have no nodes at all in our database (even after synchronising), // then we should return nothing. We aren't going to find any others @@ -975,24 +1242,19 @@ class NodeConnectionManager { ); // Skip if the node has already been contacted if (contacted.has(nextNodeId.toString())) continue; - if (ignoreRecentOffline && this.hasBackoff(nextNodeId)) continue; // Connect to the node (check if pre-existing connection exists, otherwise // create a new one) if ( - await this.pingNode( + !(await this.pingNode( nextNodeId, nextNodeAddress.address.host, nextNodeAddress.address.port, { signal: ctx.signal, - timer: pingTimeoutTime ?? this.pingTimeoutTime, + timer: pingTimeoutTime ?? this.connectionConnectTimeoutTime, }, - ) + )) ) { - await this.nodeManager!.setNode(nextNodeId, nextNodeAddress.address); - this.removeBackoff(nextNodeId); - } else { - this.increaseBackoff(nextNodeId); continue; } contacted[nextNodeId] = true; @@ -1025,11 +1287,10 @@ class NodeConnectionManager { nodeData.address.port, { signal: ctx.signal, - timer: pingTimeoutTime ?? this.pingTimeoutTime, + timer: pingTimeoutTime ?? this.connectionConnectTimeoutTime, }, )) ) { - 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 @@ -1054,22 +1315,6 @@ class NodeConnectionManager { } }); } - // If the found nodes are less than nodeBucketLimit then - // we expect that refresh buckets won't find anything new - if (Object.keys(contacted).length < this.nodeGraph.nodeBucketLimit) { - // Reset the delay on all refresh bucket tasks - for ( - let bucketIndex = 0; - bucketIndex < this.nodeGraph.nodeIdBits; - bucketIndex++ - ) { - await this.nodeManager?.updateRefreshBucketDelay( - bucketIndex, - undefined, - true, - ); - } - } return foundAddress; } @@ -1089,7 +1334,7 @@ class NodeConnectionManager { @timedCancellable( true, (nodeConnectionManager: NodeConnectionManager) => - nodeConnectionManager.connectionConnectTime, + nodeConnectionManager.connectionConnectTimeoutTime, ) public async getRemoteNodeClosestNodes( nodeId: NodeId, @@ -1160,7 +1405,7 @@ class NodeConnectionManager { @timedCancellable( true, (nodeConnectionManager: NodeConnectionManager) => - nodeConnectionManager.connectionConnectTime, + nodeConnectionManager.connectionConnectTimeoutTime, ) public async sendSignalingMessage( relayNodeId: NodeId, @@ -1224,7 +1469,7 @@ class NodeConnectionManager { @timedCancellable( true, (nodeConnectionManager: NodeConnectionManager) => - nodeConnectionManager.connectionConnectTime, + nodeConnectionManager.connectionConnectTimeoutTime, ) public async relaySignalingMessage( message: HolePunchRelayMessage, @@ -1251,7 +1496,7 @@ class NodeConnectionManager { public getSeedNodes(): Array { return Object.keys(this.seedNodes).map((nodeIdEncoded) => { const nodeId = nodesUtils.decodeNodeId(nodeIdEncoded); - if (nodeId == null) never(); + if (nodeId == null) utils.never(); return nodeId; }); } @@ -1286,7 +1531,7 @@ class NodeConnectionManager { @timedCancellable( true, (nodeConnectionManager: NodeConnectionManager) => - nodeConnectionManager.pingTimeoutTime, + nodeConnectionManager.connectionConnectTimeoutTime, ) public async pingNode( nodeId: NodeId, @@ -1386,25 +1631,6 @@ class NodeConnectionManager { return results; } - public updateConnectionConfig({ - connectionKeepAliveIntervalTime, - connectionMaxIdleTimeout, - }: { - connectionKeepAliveIntervalTime?: number; - connectionMaxIdleTimeout?: number; - }) { - if (connectionKeepAliveIntervalTime != null) { - this.connectionKeepAliveIntervalTime = connectionKeepAliveIntervalTime; - } - if (connectionMaxIdleTimeout != null) { - this.connectionMaxIdleTimeout = connectionMaxIdleTimeout; - } - this.quicServer.updateConfig({ - keepAliveIntervalTime: connectionKeepAliveIntervalTime, - maxIdleTimeout: connectionMaxIdleTimeout, - }); - } - public updateTlsConfig(tlsConfig: TLSConfig) { this.tlsConfig = tlsConfig; this.quicServer.updateConfig({ @@ -1413,34 +1639,6 @@ class NodeConnectionManager { }); } - protected hasBackoff(nodeId: NodeId): boolean { - const backoff = this.nodesBackoffMap.get(nodeId.toString()); - if (backoff == null) return false; - const currentTime = performance.now() + performance.timeOrigin; - const backOffDeadline = backoff.lastAttempt + backoff.delay; - return currentTime < backOffDeadline; - } - - protected increaseBackoff(nodeId: NodeId): void { - const backoff = this.nodesBackoffMap.get(nodeId.toString()); - const currentTime = performance.now() + performance.timeOrigin; - if (backoff == null) { - this.nodesBackoffMap.set(nodeId.toString(), { - lastAttempt: currentTime, - delay: this.backoffDefault, - }); - } else { - this.nodesBackoffMap.set(nodeId.toString(), { - lastAttempt: currentTime, - delay: backoff.delay * this.backoffMultiplier, - }); - } - } - - protected removeBackoff(nodeId: NodeId): void { - this.nodesBackoffMap.delete(nodeId.toString()); - } - /** * This attempts the NAT hole punch procedure. It will return a * `PromiseCancellable` that will resolve once the procedure times out, is diff --git a/src/nodes/NodeGraph.ts b/src/nodes/NodeGraph.ts index 911297498..c19658cee 100644 --- a/src/nodes/NodeGraph.ts +++ b/src/nodes/NodeGraph.ts @@ -17,6 +17,7 @@ import { import { IdInternal } from '@matrixai/id'; import * as nodesUtils from './utils'; import * as nodesErrors from './errors'; +import * as nodesEvents from './events'; import { getUnixtime, never } from '../utils'; /** @@ -27,6 +28,14 @@ interface NodeGraph extends CreateDestroyStartStop {} @CreateDestroyStartStop( new nodesErrors.ErrorNodeGraphRunning(), new nodesErrors.ErrorNodeGraphDestroyed(), + { + eventStart: nodesEvents.EventNodeGraphStart, + eventStarted: nodesEvents.EventNodeGraphStarted, + eventStop: nodesEvents.EventNodeGraphStop, + eventStopped: nodesEvents.EventNodeGraphStopped, + eventDestroy: nodesEvents.EventNodeGraphDestroy, + eventDestroyed: nodesEvents.EventNodeGraphDestroyed, + }, ) class NodeGraph { public static async createNodeGraph({ diff --git a/src/nodes/NodeManager.ts b/src/nodes/NodeManager.ts index 7fd1a012e..c1c455caf 100644 --- a/src/nodes/NodeManager.ts +++ b/src/nodes/NodeManager.ts @@ -24,11 +24,11 @@ import type { PromiseCancellable } from '@matrixai/async-cancellable'; import type { Host, Port } from '../network/types'; import type { SignedTokenEncoded } from '../tokens/types'; import type { ClaimLinkNode } from '../claims/payloads/index'; -import type { AgentClaimMessage } from '../agent/handlers/types'; import type { AgentRPCRequestParams, AgentRPCResponseResult, -} from '../agent/types'; + AgentClaimMessage, +} from './agent/types'; import type { ContextTimedInput } from '@matrixai/contexts/dist/types'; import Logger from '@matrixai/logger'; import { StartStop, ready } from '@matrixai/async-init/dist/StartStop'; @@ -37,6 +37,7 @@ import { IdInternal } from '@matrixai/id'; import { timedCancellable, context } from '@matrixai/contexts/dist/decorators'; import * as nodesErrors from './errors'; import * as nodesUtils from './utils'; +import * as nodesEvents from './events'; import * as claimsUtils from '../claims/utils'; import * as tasksErrors from '../tasks/errors'; import * as claimsErrors from '../claims/errors'; @@ -53,7 +54,12 @@ const abortEphemeralTaskReason = Symbol('abort ephemeral task reason'); const abortSingletonTaskReason = Symbol('abort singleton task reason'); interface NodeManager extends StartStop {} -@StartStop() +@StartStop({ + eventStart: nodesEvents.EventNodeManagerStart, + eventStarted: nodesEvents.EventNodeManagerStarted, + eventStop: nodesEvents.EventNodeManagerStop, + eventStopped: nodesEvents.EventNodeManagerStopped, +}) class NodeManager { protected db: DB; protected logger: Logger; @@ -68,6 +74,11 @@ class NodeManager { protected retrySeedConnectionsDelay: number; protected pendingNodes: Map> = new Map(); + /** + * Time used to establish `NodeConnection` + */ + public readonly connectionConnectTimeoutTime: number; + public readonly basePath = this.constructor.name; protected refreshBucketHandler: TaskHandler = async ( ctx, @@ -76,7 +87,7 @@ class NodeManager { ) => { await this.refreshBucket( bucketIndex, - this.nodeConnectionManager.pingTimeoutTime, + this.connectionConnectTimeoutTime, ctx, ); // When completed reschedule the task @@ -102,7 +113,7 @@ class NodeManager { ) => { await this.garbageCollectBucket( bucketIndex, - this.nodeConnectionManager.pingTimeoutTime, + this.connectionConnectTimeoutTime, ctx, ); // Checking for any new pending tasks @@ -190,6 +201,20 @@ class NodeManager { public readonly checkSeedConnectionsHandlerId: TaskHandlerId = `${this.basePath}.${this.checkSeedConnectionsHandler.name}.checkSeedConnectionsHandler` as TaskHandlerId; + protected handleNodeConnectionEvent = async ( + e: nodesEvents.EventNodeConnectionManagerConnection, + ) => { + await this.setNode( + e.detail.remoteNodeId, + { + host: e.detail.remoteHost, + port: e.detail.remotePort, + }, + false, + false, + ); + }; + constructor({ db, keyRing, @@ -259,11 +284,21 @@ class NodeManager { lazy: true, path: [this.basePath, this.checkSeedConnectionsHandlerId], }); + // Add handling for connections + this.nodeConnectionManager.addEventListener( + nodesEvents.EventNodeConnectionManagerConnection.name, + this.handleNodeConnectionEvent, + ); this.logger.info(`Started ${this.constructor.name}`); } public async stop() { this.logger.info(`Stopping ${this.constructor.name}`); + // Remove handling for connections + this.nodeConnectionManager.removeEventListener( + nodesEvents.EventNodeConnectionManagerConnection.name, + this.handleNodeConnectionEvent, + ); this.logger.info('Cancelling ephemeral tasks'); if (this.taskManager.isProcessing()) { throw new tasksErrors.ErrorTaskManagerProcessing(); @@ -300,8 +335,7 @@ class NodeManager { ): PromiseCancellable; @timedCancellable( true, - (nodeManager: NodeManager) => - nodeManager.nodeConnectionManager.pingTimeoutTime, + (nodeManager: NodeManager) => nodeManager.connectionConnectTimeoutTime, ) public async pingNode( nodeId: NodeId, @@ -314,8 +348,7 @@ class NodeManager { address ?? (await this.nodeConnectionManager.findNode( nodeId, - false, - this.nodeConnectionManager.pingTimeoutTime, + this.connectionConnectTimeoutTime, ctx, )); if (targetAddress == null) { @@ -652,7 +685,7 @@ class NodeManager { * @param block - When true it will wait for any garbage collection to finish before returning. * @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 pingTimeoutTime - Timeout for each ping opearation during garbage collection. + * @param pingTimeoutTime - Timeout for each ping operation during garbage collection. * @param ctx * @param tran */ @@ -672,7 +705,7 @@ class NodeManager { nodeAddress: NodeAddress, block: boolean = false, force: boolean = false, - pingTimeoutTime: number | undefined, + pingTimeoutTime: number = this.connectionConnectTimeoutTime, @context ctx: ContextTimed, tran?: DBTransaction, ): Promise { @@ -760,7 +793,7 @@ class NodeManager { nodeId, nodeAddress, block, - pingTimeoutTime ?? this.nodeConnectionManager.pingTimeoutTime, + pingTimeoutTime, ctx, tran, ); @@ -776,7 +809,7 @@ class NodeManager { @timedCancellable(true) protected async garbageCollectBucket( bucketIndex: number, - pingTimeoutTime: number | undefined, + pingTimeoutTime: number = this.connectionConnectTimeoutTime, @context ctx: ContextTimed, tran?: DBTransaction, ): Promise { @@ -823,8 +856,7 @@ class NodeManager { // Ping and remove or update node in bucket const pingCtx = { signal: ctx.signal, - timer: - pingTimeoutTime ?? this.nodeConnectionManager.pingTimeoutTime, + timer: pingTimeoutTime, }; const nodeAddress = await this.getNodeAddress(nodeId, tran); if (nodeAddress == null) never(); @@ -878,7 +910,7 @@ class NodeManager { nodeId: NodeId, nodeAddress: NodeAddress, block: boolean = false, - pingTimeoutTime: number | undefined, + pingTimeoutTime: number = this.connectionConnectTimeoutTime, ctx: ContextTimed, tran?: DBTransaction, ): Promise { @@ -892,12 +924,7 @@ class NodeManager { // If set to blocking we just run the GC operation here // without setting up a new task if (block) { - await this.garbageCollectBucket( - bucketIndex, - pingTimeoutTime ?? this.nodeConnectionManager.pingTimeoutTime, - ctx, - tran, - ); + await this.garbageCollectBucket(bucketIndex, pingTimeoutTime, ctx, tran); return; } await this.setupGCTask(bucketIndex); @@ -960,7 +987,7 @@ class NodeManager { /** * 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 + * Connections during the search will share node information with other * nodes. * @param bucketIndex * @param pingTimeoutTime @@ -986,7 +1013,6 @@ class NodeManager { // We then need to start a findNode procedure await this.nodeConnectionManager.findNode( bucketRandomNodeId, - true, pingTimeoutTime, ctx, ); @@ -1173,9 +1199,7 @@ class NodeManager { await this.db.withTransactionF(async (tran) => seedNodes.map( async (seedNode) => - ( - await this.nodeGraph.getNode(seedNode, tran) - )?.address, + (await this.nodeGraph.getNode(seedNode, tran))?.address, ), ), ); diff --git a/src/nodes/agent/callers/index.ts b/src/nodes/agent/callers/index.ts new file mode 100644 index 000000000..32e6c916e --- /dev/null +++ b/src/nodes/agent/callers/index.ts @@ -0,0 +1,35 @@ +import nodesClaimsGet from './nodesClaimsGet'; +import nodesClosestLocalNodesGet from './nodesClosestLocalNodesGet'; +import nodesCrossSignClaim from './nodesCrossSignClaim'; +import nodesHolePunchMessageSend from './nodesHolePunchMessageSend'; +import notificationsSend from './notificationsSend'; +import vaultsGitInfoGet from './vaultsGitInfoGet'; +import vaultsGitPackGet from './vaultsGitPackGet'; +import vaultsScan from './vaultsScan'; + +/** + * Client manifest + */ +const manifestClient = { + nodesClaimsGet, + nodesClosestLocalNodesGet, + nodesCrossSignClaim, + nodesHolePunchMessageSend, + notificationsSend, + vaultsGitInfoGet, + vaultsGitPackGet, + vaultsScan, +}; + +export default manifestClient; + +export { + nodesClaimsGet, + nodesClosestLocalNodesGet, + nodesCrossSignClaim, + nodesHolePunchMessageSend, + notificationsSend, + vaultsGitInfoGet, + vaultsGitPackGet, + vaultsScan, +}; diff --git a/src/nodes/agent/callers/nodesClaimsGet.ts b/src/nodes/agent/callers/nodesClaimsGet.ts new file mode 100644 index 000000000..7b9af89fe --- /dev/null +++ b/src/nodes/agent/callers/nodesClaimsGet.ts @@ -0,0 +1,12 @@ +import type NodesClaimsGetHandler from '../handlers/NodesClaimsGet'; +import type { HandlerTypes } from '../../../rpc/types'; +import { ServerCaller } from '../../../rpc/callers'; + +type CallerTypes = HandlerTypes; + +const nodesClaimsGet = new ServerCaller< + CallerTypes['input'], + CallerTypes['output'] +>(); + +export default nodesClaimsGet; diff --git a/src/nodes/agent/callers/nodesClosestLocalNodesGet.ts b/src/nodes/agent/callers/nodesClosestLocalNodesGet.ts new file mode 100644 index 000000000..daaff9f4b --- /dev/null +++ b/src/nodes/agent/callers/nodesClosestLocalNodesGet.ts @@ -0,0 +1,12 @@ +import type NodesClosestLocalNodesGetHandler from '../handlers/NodesClosestLocalNodesGet'; +import type { HandlerTypes } from '../../../rpc/types'; +import { ServerCaller } from '../../../rpc/callers'; + +type CallerTypes = HandlerTypes; + +const nodesClosestLocalNodesGet = new ServerCaller< + CallerTypes['input'], + CallerTypes['output'] +>(); + +export default nodesClosestLocalNodesGet; diff --git a/src/nodes/agent/callers/nodesCrossSignClaim.ts b/src/nodes/agent/callers/nodesCrossSignClaim.ts new file mode 100644 index 000000000..b3462aaa5 --- /dev/null +++ b/src/nodes/agent/callers/nodesCrossSignClaim.ts @@ -0,0 +1,12 @@ +import type NodesCrossSignClaimHandler from '../handlers/NodesCrossSignClaim'; +import type { HandlerTypes } from '../../../rpc/types'; +import { DuplexCaller } from '../../../rpc/callers'; + +type CallerTypes = HandlerTypes; + +const nodesCrossSignClaim = new DuplexCaller< + CallerTypes['input'], + CallerTypes['output'] +>(); + +export default nodesCrossSignClaim; diff --git a/src/nodes/agent/callers/nodesHolePunchMessageSend.ts b/src/nodes/agent/callers/nodesHolePunchMessageSend.ts new file mode 100644 index 000000000..6c5234601 --- /dev/null +++ b/src/nodes/agent/callers/nodesHolePunchMessageSend.ts @@ -0,0 +1,12 @@ +import type NodesHolePunchMessageSendHandler from '../handlers/NodesHolePunchMessageSend'; +import type { HandlerTypes } from '../../../rpc/types'; +import { UnaryCaller } from '../../../rpc/callers'; + +type CallerTypes = HandlerTypes; + +const nodesHolePunchMessageSend = new UnaryCaller< + CallerTypes['input'], + CallerTypes['output'] +>(); + +export default nodesHolePunchMessageSend; diff --git a/src/nodes/agent/callers/notificationsSend.ts b/src/nodes/agent/callers/notificationsSend.ts new file mode 100644 index 000000000..5bc32ef47 --- /dev/null +++ b/src/nodes/agent/callers/notificationsSend.ts @@ -0,0 +1,12 @@ +import type NotificationsSendHandler from '../handlers/NotificationsSend'; +import type { HandlerTypes } from '../../../rpc/types'; +import { UnaryCaller } from '../../../rpc/callers'; + +type CallerTypes = HandlerTypes; + +const notificationsSend = new UnaryCaller< + CallerTypes['input'], + CallerTypes['output'] +>(); + +export default notificationsSend; diff --git a/src/nodes/agent/callers/vaultsGitInfoGet.ts b/src/nodes/agent/callers/vaultsGitInfoGet.ts new file mode 100644 index 000000000..f118ae66b --- /dev/null +++ b/src/nodes/agent/callers/vaultsGitInfoGet.ts @@ -0,0 +1,5 @@ +import { RawCaller } from '../../../rpc/callers'; + +const vaultsGitInfoGet = new RawCaller(); + +export default vaultsGitInfoGet; diff --git a/src/nodes/agent/callers/vaultsGitPackGet.ts b/src/nodes/agent/callers/vaultsGitPackGet.ts new file mode 100644 index 000000000..19fda4df3 --- /dev/null +++ b/src/nodes/agent/callers/vaultsGitPackGet.ts @@ -0,0 +1,5 @@ +import { RawCaller } from '../../../rpc/callers'; + +const vaultsGitPackGet = new RawCaller(); + +export default vaultsGitPackGet; diff --git a/src/nodes/agent/callers/vaultsScan.ts b/src/nodes/agent/callers/vaultsScan.ts new file mode 100644 index 000000000..944aff8bd --- /dev/null +++ b/src/nodes/agent/callers/vaultsScan.ts @@ -0,0 +1,12 @@ +import type VaultsScanHandler from '../handlers/VaultsScan'; +import type { HandlerTypes } from '../../../rpc/types'; +import { ServerCaller } from '../../../rpc/callers'; + +type CallerTypes = HandlerTypes; + +const vaultsScan = new ServerCaller< + CallerTypes['input'], + CallerTypes['output'] +>(); + +export default vaultsScan; diff --git a/src/agent/errors.ts b/src/nodes/agent/errors.ts similarity index 73% rename from src/agent/errors.ts rename to src/nodes/agent/errors.ts index 665374635..b52b6f9ac 100644 --- a/src/agent/errors.ts +++ b/src/nodes/agent/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../../ErrorPolykey'; +import sysexits from '../../utils/sysexits'; class ErrorAgent extends ErrorPolykey {} diff --git a/src/agent/handlers/nodesClaimsGet.ts b/src/nodes/agent/handlers/NodesClaimsGet.ts similarity index 56% rename from src/agent/handlers/nodesClaimsGet.ts rename to src/nodes/agent/handlers/NodesClaimsGet.ts index e0de1ae9f..efc572f23 100644 --- a/src/agent/handlers/nodesClaimsGet.ts +++ b/src/nodes/agent/handlers/NodesClaimsGet.ts @@ -1,11 +1,18 @@ import type { DB } from '@matrixai/db'; -import type { ClaimIdMessage, AgentClaimMessage } from './types'; -import type Sigchain from '../../sigchain/Sigchain'; -import type { AgentRPCRequestParams, AgentRPCResponseResult } from '../types'; -import * as claimsUtils from '../../claims/utils'; -import { ServerHandler } from '../../rpc/handlers'; +import type Sigchain from '../../../sigchain/Sigchain'; +import type { + AgentRPCRequestParams, + AgentRPCResponseResult, + ClaimIdMessage, + AgentClaimMessage, +} from '../types'; +import * as claimsUtils from '../../../claims/utils'; +import { ServerHandler } from '../../../rpc/handlers'; -class NodesClaimsGetHandler extends ServerHandler< +/** + * Gets the sigchain claims of a node + */ +class NodesClaimsGet extends ServerHandler< { sigchain: Sigchain; db: DB; @@ -13,13 +20,13 @@ class NodesClaimsGetHandler extends ServerHandler< AgentRPCRequestParams, AgentRPCResponseResult > { - public async *handle( + public handle = async function* ( _input: ClaimIdMessage, ): AsyncGenerator> { const { sigchain, db } = this.container; - yield* db.withTransactionG(async function* ( - tran, - ): AsyncGenerator> { + yield* db.withTransactionG(async function* (tran): AsyncGenerator< + AgentRPCResponseResult + > { for await (const [claimId, signedClaim] of sigchain.getSignedClaims( { /* seek: seekClaimId,*/ order: 'asc' }, tran, @@ -32,7 +39,7 @@ class NodesClaimsGetHandler extends ServerHandler< yield response; } }); - } + }; } -export { NodesClaimsGetHandler }; +export default NodesClaimsGet; diff --git a/src/agent/handlers/nodesClosestLocalNodesGet.ts b/src/nodes/agent/handlers/NodesClosestLocalNodesGet.ts similarity index 52% rename from src/agent/handlers/nodesClosestLocalNodesGet.ts rename to src/nodes/agent/handlers/NodesClosestLocalNodesGet.ts index b31d72779..0ee9f7f2f 100644 --- a/src/agent/handlers/nodesClosestLocalNodesGet.ts +++ b/src/nodes/agent/handlers/NodesClosestLocalNodesGet.ts @@ -1,15 +1,21 @@ import type { DB } from '@matrixai/db'; -import type { NodeAddressMessage, NodeIdMessage } from './types'; -import type { AgentRPCRequestParams, AgentRPCResponseResult } from '../types'; -import type { NodeGraph } from '../../nodes'; -import type { NodeId } from '../../ids'; -import { validateSync } from '../../validation'; -import { matchSync } from '../../utils'; -import * as validationUtils from '../../validation/utils'; -import * as nodesUtils from '../../nodes/utils'; -import { ServerHandler } from '../../rpc/handlers'; +import type { + AgentRPCRequestParams, + AgentRPCResponseResult, + NodeAddressMessage, + NodeIdMessage, +} from '../types'; +import type NodeGraph from '../../NodeGraph'; +import type { NodeId } from '../../../ids'; +import * as validation from '../../../validation'; +import * as nodesUtils from '../../utils'; +import * as utils from '../../../utils'; +import { ServerHandler } from '../../../rpc/handlers'; -class NodesClosestLocalNodesGetHandler extends ServerHandler< +/** + * Gets the closest local nodes to a target node + */ +class NodesClosestLocalNodesGet extends ServerHandler< { nodeGraph: NodeGraph; db: DB; @@ -17,7 +23,7 @@ class NodesClosestLocalNodesGetHandler extends ServerHandler< AgentRPCRequestParams, AgentRPCResponseResult > { - public async *handle( + public handle = async function* ( input: AgentRPCRequestParams, ): AsyncGenerator> { const { nodeGraph, db } = this.container; @@ -26,10 +32,10 @@ class NodesClosestLocalNodesGetHandler extends ServerHandler< nodeId, }: { nodeId: NodeId; - } = validateSync( + } = validation.validateSync( (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], + return utils.matchSync(keyPath)( + [['nodeId'], () => validation.utils.parseNodeId(value)], () => value, ); }, @@ -38,9 +44,9 @@ class NodesClosestLocalNodesGetHandler extends ServerHandler< }, ); // Get all local nodes that are closest to the target node from the request - return yield* db.withTransactionG(async function* ( - tran, - ): AsyncGenerator> { + return yield* db.withTransactionG(async function* (tran): AsyncGenerator< + AgentRPCResponseResult + > { const closestNodes = await nodeGraph.getClosestNodes( nodeId, undefined, @@ -54,7 +60,7 @@ class NodesClosestLocalNodesGetHandler extends ServerHandler< }; } }); - } + }; } -export { NodesClosestLocalNodesGetHandler }; +export default NodesClosestLocalNodesGet; diff --git a/src/agent/handlers/nodesCrossSignClaim.ts b/src/nodes/agent/handlers/NodesCrossSignClaim.ts similarity index 67% rename from src/agent/handlers/nodesCrossSignClaim.ts rename to src/nodes/agent/handlers/NodesCrossSignClaim.ts index 36d4dd9a0..1a475820f 100644 --- a/src/agent/handlers/nodesCrossSignClaim.ts +++ b/src/nodes/agent/handlers/NodesCrossSignClaim.ts @@ -1,13 +1,19 @@ -import type { AgentClaimMessage } from './types'; -import type { AgentRPCRequestParams, AgentRPCResponseResult } from '../types'; -import type ACL from '../../acl/ACL'; -import type NodeManager from '../../nodes/NodeManager'; +import type { + AgentRPCRequestParams, + AgentRPCResponseResult, + AgentClaimMessage, +} from '../types'; +import type NodeManager from '../../NodeManager'; +import type ACL from '../../../acl/ACL'; import * as agentErrors from '../errors'; import * as agentUtils from '../utils'; -import * as nodesErrors from '../../nodes/errors'; -import { DuplexHandler } from '../../rpc/handlers'; +import * as nodesErrors from '../../errors'; +import { DuplexHandler } from '../../../rpc/handlers'; -class NodesCrossSignClaimHandler extends DuplexHandler< +/** + * Claims a node + */ +class NodesCrossSignClaim extends DuplexHandler< { acl: ACL; nodeManager: NodeManager; @@ -15,7 +21,7 @@ class NodesCrossSignClaimHandler extends DuplexHandler< AgentRPCRequestParams, AgentRPCResponseResult > { - public async *handle( + public handle = async function* ( input: AsyncIterableIterator>, _cancel, meta, @@ -32,7 +38,7 @@ class NodesCrossSignClaimHandler extends DuplexHandler< } // Handle claiming the node yield* nodeManager.handleClaimNode(requestingNodeId, input); - } + }; } -export { NodesCrossSignClaimHandler }; +export default NodesCrossSignClaim; diff --git a/src/agent/handlers/nodesHolePunchMessageSend.ts b/src/nodes/agent/handlers/NodesHolePunchMessageSend.ts similarity index 75% rename from src/agent/handlers/nodesHolePunchMessageSend.ts rename to src/nodes/agent/handlers/NodesHolePunchMessageSend.ts index 0b80fb035..297d6ea62 100644 --- a/src/agent/handlers/nodesHolePunchMessageSend.ts +++ b/src/nodes/agent/handlers/NodesHolePunchMessageSend.ts @@ -1,21 +1,26 @@ import type { DB } from '@matrixai/db'; import type Logger from '@matrixai/logger'; -import type { HolePunchRelayMessage } from './types'; -import type { AgentRPCRequestParams, AgentRPCResponseResult } from '../types'; -import type NodeConnectionManager from '../../nodes/NodeConnectionManager'; -import type NodeManager from '../../nodes/NodeManager'; -import type KeyRing from '../../keys/KeyRing'; -import type { Host, Port } from '../../network/types'; -import type { NodeId } from '../../ids'; -import * as agentErrors from '../errors'; +import type { + AgentRPCRequestParams, + AgentRPCResponseResult, + HolePunchRelayMessage, +} from '../types'; +import type NodeConnectionManager from '../../NodeConnectionManager'; +import type NodeManager from '../../NodeManager'; +import type KeyRing from '../../../keys/KeyRing'; +import type { Host, Port } from '../../../network/types'; +import type { NodeId } from '../../../ids'; import * as agentUtils from '../utils'; -import { validateSync } from '../../validation'; -import { matchSync } from '../../utils'; -import * as validationUtils from '../../validation/utils'; -import * as nodesUtils from '../../nodes/utils'; -import { UnaryHandler } from '../../rpc/handlers'; +import * as agentErrors from '../errors'; +import * as nodesUtils from '../../utils'; +import * as validation from '../../../validation'; +import * as utils from '../../../utils'; +import { UnaryHandler } from '../../../rpc/handlers'; -class NodesHolePunchMessageSendHandler extends UnaryHandler< +/** + * Sends a hole punch message to a node + */ +class NodesHolePunchMessageSend extends UnaryHandler< { db: DB; nodeConnectionManager: NodeConnectionManager; @@ -26,11 +31,11 @@ class NodesHolePunchMessageSendHandler extends UnaryHandler< AgentRPCRequestParams, AgentRPCResponseResult > { - public async handle( + public handle = async ( input: AgentRPCRequestParams, _cancel, meta, - ): Promise { + ): Promise => { const { db, nodeConnectionManager, keyRing, nodeManager, logger } = this.container; const { @@ -39,13 +44,13 @@ class NodesHolePunchMessageSendHandler extends UnaryHandler< }: { targetId: NodeId; sourceId: NodeId; - } = validateSync( + } = validation.validateSync( (keyPath, value) => { - return matchSync(keyPath)( + return utils.matchSync(keyPath)( [ ['targetId'], ['sourceId'], - () => validationUtils.parseNodeId(value), + () => validation.utils.parseNodeId(value), ], () => value, ); @@ -105,7 +110,7 @@ class NodesHolePunchMessageSendHandler extends UnaryHandler< } }); return {}; - } + }; } -export { NodesHolePunchMessageSendHandler }; +export default NodesHolePunchMessageSend; diff --git a/src/agent/handlers/notificationsSend.ts b/src/nodes/agent/handlers/NotificationsSend.ts similarity index 50% rename from src/agent/handlers/notificationsSend.ts rename to src/nodes/agent/handlers/NotificationsSend.ts index ec0a55c50..82e03d506 100644 --- a/src/agent/handlers/notificationsSend.ts +++ b/src/nodes/agent/handlers/NotificationsSend.ts @@ -1,13 +1,19 @@ import type { DB } from '@matrixai/db'; -import type { SignedNotificationEncoded } from './types'; -import type { AgentRPCRequestParams, AgentRPCResponseResult } from '../types'; -import type KeyRing from '../../keys/KeyRing'; -import type NotificationsManager from '../../notifications/NotificationsManager'; -import type { SignedNotification } from '../../notifications/types'; -import { UnaryHandler } from '../../rpc/handlers'; -import * as notificationsUtils from '../../notifications/utils'; +import type { + AgentRPCRequestParams, + AgentRPCResponseResult, + SignedNotificationEncoded, +} from '../types'; +import type KeyRing from '../../../keys/KeyRing'; +import type NotificationsManager from '../../../notifications/NotificationsManager'; +import type { SignedNotification } from '../../../notifications/types'; +import * as notificationsUtils from '../../../notifications/utils'; +import { UnaryHandler } from '../../../rpc/handlers'; -class NotificationsSendHandler extends UnaryHandler< +/** + * Sends a notification to a node + */ +class NotificationsSend extends UnaryHandler< { db: DB; keyRing: KeyRing; @@ -16,9 +22,9 @@ class NotificationsSendHandler extends UnaryHandler< AgentRPCRequestParams, AgentRPCResponseResult > { - public async handle( + public handle = async ( input: AgentRPCRequestParams, - ): Promise { + ): Promise => { const { db, keyRing, notificationsManager } = this.container; const notification = await notificationsUtils.verifyAndDecodeNotif( input.signedNotificationEncoded as SignedNotification, @@ -28,7 +34,7 @@ class NotificationsSendHandler extends UnaryHandler< notificationsManager.receiveNotification(notification, tran), ); return {}; - } + }; } -export { NotificationsSendHandler }; +export default NotificationsSend; diff --git a/src/agent/handlers/vaultsGitInfoGet.ts b/src/nodes/agent/handlers/VaultsGitInfoGet.ts similarity index 77% rename from src/agent/handlers/vaultsGitInfoGet.ts rename to src/nodes/agent/handlers/VaultsGitInfoGet.ts index e0f6b4623..ded527a59 100644 --- a/src/agent/handlers/vaultsGitInfoGet.ts +++ b/src/nodes/agent/handlers/VaultsGitInfoGet.ts @@ -1,47 +1,51 @@ import type { DB } from '@matrixai/db'; -import type { VaultManager } from '../../vaults'; -import type { ACL } from '../../acl'; import type Logger from '@matrixai/logger'; -import type { JSONRPCRequest } from '../../rpc/types'; import type { ContextTimed } from '@matrixai/contexts'; -import type { JSONValue } from '../../types'; +import type ACL from '../../../acl/ACL'; +import type VaultManager from '../../../vaults/VaultManager'; +import type { JSONRPCRequest } from '../../../rpc/types'; +import type { JSONValue } from '../../../types'; import { ReadableStream } from 'stream/web'; import * as agentErrors from '../errors'; -import * as vaultsUtils from '../../vaults/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import { RawHandler } from '../../rpc/handlers'; -import { never } from '../../utils'; -import * as validationUtils from '../../validation/utils'; -import * as nodesUtils from '../../nodes/utils'; +import * as validation from '../../../validation'; +import * as vaultsUtils from '../../../vaults/utils'; +import * as vaultsErrors from '../../../vaults/errors'; +import * as nodesUtils from '../../utils'; import * as agentUtils from '../utils'; -import * as utils from '../../utils'; +import * as utils from '../../../utils'; +import { RawHandler } from '../../../rpc/handlers'; -class VaultsGitInfoGetHandler extends RawHandler<{ +/** + * Gets the git info of a vault. + */ +class VaultsGitInfoGet extends RawHandler<{ db: DB; vaultManager: VaultManager; acl: ACL; logger: Logger; }> { - public async handle( + public handle = async ( input: [JSONRPCRequest, ReadableStream], _cancel, meta: Record | undefined, _ctx: ContextTimed, // TODO: use - ): Promise<[JSONValue, ReadableStream]> { + ): Promise<[JSONValue, ReadableStream]> => { const { db, vaultManager, acl } = this.container; const [headerMessage, inputStream] = input; await inputStream.cancel(); const params = headerMessage.params; - if (params == null || !utils.isObject(params)) never(); + if (params == null || !utils.isObject(params)) utils.never(); if ( !('vaultNameOrId' in params) || typeof params.vaultNameOrId != 'string' ) { - never(); + utils.never(); + } + if (!('action' in params) || typeof params.action != 'string') { + utils.never(); } - if (!('action' in params) || typeof params.action != 'string') never(); const vaultNameOrId = params.vaultNameOrId; - const actionType = validationUtils.parseVaultAction(params.action); + const actionType = validation.utils.parseVaultAction(params.action); const data = await db.withTransactionF(async (tran) => { const vaultIdFromName = await vaultManager.getVaultId( vaultNameOrId, @@ -108,7 +112,7 @@ class VaultsGitInfoGetHandler extends RawHandler<{ }, stream, ]; - } + }; } -export { VaultsGitInfoGetHandler }; +export default VaultsGitInfoGet; diff --git a/src/agent/handlers/vaultsGitPackGet.ts b/src/nodes/agent/handlers/VaultsGitPackGet.ts similarity index 74% rename from src/agent/handlers/vaultsGitPackGet.ts rename to src/nodes/agent/handlers/VaultsGitPackGet.ts index 6a01f0572..5d1dc0de1 100644 --- a/src/agent/handlers/vaultsGitPackGet.ts +++ b/src/nodes/agent/handlers/VaultsGitPackGet.ts @@ -1,31 +1,33 @@ import type { DB } from '@matrixai/db'; -import type { VaultName } from '../../vaults/types'; -import type VaultManager from '../../vaults/VaultManager'; -import type ACL from '../../acl/ACL'; -import type { JSONValue } from '../../types'; import type { PassThrough } from 'readable-stream'; -import type { JSONRPCRequest } from '../../rpc/types'; +import type { VaultName } from '../../../vaults/types'; +import type ACL from '../../../acl/ACL'; +import type VaultManager from '../../../vaults/VaultManager'; +import type { JSONRPCRequest } from '../../../rpc/types'; +import type { JSONValue } from '../../../types'; import { ReadableStream } from 'stream/web'; -import * as utils from '../../utils'; import * as agentErrors from '../errors'; import * as agentUtils from '../utils'; -import * as nodesUtils from '../../nodes/utils'; -import * as vaultsUtils from '../../vaults/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import { never } from '../../utils'; -import * as validationUtils from '../../validation/utils'; -import { RawHandler } from '../../rpc/handlers'; +import * as validation from '../../../validation'; +import * as nodesUtils from '../../utils'; +import * as vaultsUtils from '../../../vaults/utils'; +import * as vaultsErrors from '../../../vaults/errors'; +import * as utils from '../../../utils'; +import { RawHandler } from '../../../rpc/handlers'; -class VaultsGitPackGetHandler extends RawHandler<{ +/** + * Gets the git pack of a vault. + */ +class VaultsGitPackGet extends RawHandler<{ vaultManager: VaultManager; acl: ACL; db: DB; }> { - public async handle( + public handle = async ( input: [JSONRPCRequest, ReadableStream], _cancel, meta, - ): Promise<[JSONValue, ReadableStream]> { + ): Promise<[JSONValue, ReadableStream]> => { const { vaultManager, acl, db } = this.container; const [headerMessage, inputStream] = input; const requestingNodeId = agentUtils.nodeIdFromMeta(meta); @@ -34,15 +36,15 @@ class VaultsGitPackGetHandler extends RawHandler<{ } const nodeIdEncoded = nodesUtils.encodeNodeId(requestingNodeId); const params = headerMessage.params; - if (params == null || !utils.isObject(params)) never(); + if (params == null || !utils.isObject(params)) utils.never(); if (!('nameOrId' in params) || typeof params.nameOrId != 'string') { - never(); + utils.never(); } if (!('vaultAction' in params) || typeof params.vaultAction != 'string') { - never(); + utils.never(); } const nameOrId = params.nameOrId; - const actionType = validationUtils.parseVaultAction(params.vaultAction); + const actionType = validation.utils.parseVaultAction(params.vaultAction); const [vaultIdFromName, permissions] = await db.withTransactionF( async (tran) => { const vaultIdFromName = await vaultManager.getVaultId( @@ -103,7 +105,7 @@ class VaultsGitPackGetHandler extends RawHandler<{ }, }); return [null, outputStream]; - } + }; } -export { VaultsGitPackGetHandler }; +export default VaultsGitPackGet; diff --git a/src/agent/handlers/vaultsScan.ts b/src/nodes/agent/handlers/VaultsScan.ts similarity index 64% rename from src/agent/handlers/vaultsScan.ts rename to src/nodes/agent/handlers/VaultsScan.ts index bb745da81..d4d61acfd 100644 --- a/src/agent/handlers/vaultsScan.ts +++ b/src/nodes/agent/handlers/VaultsScan.ts @@ -1,13 +1,19 @@ import type { DB } from '@matrixai/db'; -import type { VaultsScanMessage } from './types'; -import type { AgentRPCRequestParams, AgentRPCResponseResult } from '../types'; -import type VaultManager from '../../vaults/VaultManager'; +import type { + AgentRPCRequestParams, + AgentRPCResponseResult, + VaultsScanMessage, +} from '../types'; +import type VaultManager from '../../../vaults/VaultManager'; import * as agentErrors from '../errors'; import * as agentUtils from '../utils'; -import { ServerHandler } from '../../rpc/handlers'; -import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsUtils from '../../../vaults/utils'; +import { ServerHandler } from '../../../rpc/handlers'; -class VaultsScanHandler extends ServerHandler< +/** + * Scan vaults. + */ +class VaultsScan extends ServerHandler< { vaultManager: VaultManager; db: DB; @@ -15,7 +21,7 @@ class VaultsScanHandler extends ServerHandler< AgentRPCRequestParams, AgentRPCResponseResult > { - public async *handle( + public handle = async function* ( input: AgentRPCRequestParams, _cancel, meta, @@ -25,9 +31,9 @@ class VaultsScanHandler extends ServerHandler< if (requestingNodeId == null) { throw new agentErrors.ErrorAgentNodeIdMissing(); } - yield* db.withTransactionG(async function* ( - tran, - ): AsyncGenerator> { + yield* db.withTransactionG(async function* (tran): AsyncGenerator< + AgentRPCResponseResult + > { const listResponse = vaultManager.handleScanVaults( requestingNodeId, tran, @@ -44,7 +50,7 @@ class VaultsScanHandler extends ServerHandler< }; } }); - } + }; } -export { VaultsScanHandler }; +export default VaultsScan; diff --git a/src/nodes/agent/handlers/index.ts b/src/nodes/agent/handlers/index.ts new file mode 100644 index 000000000..ee8e72914 --- /dev/null +++ b/src/nodes/agent/handlers/index.ts @@ -0,0 +1,58 @@ +import type { DB } from '@matrixai/db'; +import type Logger from '@matrixai/logger'; +import type KeyRing from '../../../keys/KeyRing'; +import type Sigchain from '../../../sigchain/Sigchain'; +import type ACL from '../../../acl/ACL'; +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 VaultManager from '../../../vaults/VaultManager'; +import NodesClaimsGet from './NodesClaimsGet'; +import NodesClosestLocalNodesGet from './NodesClosestLocalNodesGet'; +import NodesCrossSignClaim from './NodesCrossSignClaim'; +import NodesHolePunchMessageSend from './NodesHolePunchMessageSend'; +import NotificationsSend from './NotificationsSend'; +import VaultsGitInfoGet from './VaultsGitInfoGet'; +import VaultsGitPackGet from './VaultsGitPackGet'; +import VaultsScan from './VaultsScan'; + +/** + * Server manifest factory. + */ +const manifestServer = (container: { + db: DB; + sigchain: Sigchain; + nodeGraph: NodeGraph; + acl: ACL; + nodeManager: NodeManager; + nodeConnectionManager: NodeConnectionManager; + keyRing: KeyRing; + logger: Logger; + notificationsManager: NotificationsManager; + vaultManager: VaultManager; +}) => { + return { + nodesClaimsGet: new NodesClaimsGet(container), + nodesClosestLocalNodesGet: new NodesClosestLocalNodesGet(container), + nodesCrossSignClaim: new NodesCrossSignClaim(container), + nodesHolePunchMessageSend: new NodesHolePunchMessageSend(container), + notificationsSend: new NotificationsSend(container), + vaultsGitInfoGet: new VaultsGitInfoGet(container), + vaultsGitPackGet: new VaultsGitPackGet(container), + vaultsScan: new VaultsScan(container), + }; +}; + +export default manifestServer; + +export { + NodesClaimsGet, + NodesClosestLocalNodesGet, + NodesCrossSignClaim, + NodesHolePunchMessageSend, + NotificationsSend, + VaultsGitInfoGet, + VaultsGitPackGet, + VaultsScan, +}; diff --git a/src/nodes/agent/index.ts b/src/nodes/agent/index.ts new file mode 100644 index 000000000..a26442f67 --- /dev/null +++ b/src/nodes/agent/index.ts @@ -0,0 +1,7 @@ +export { default as manifestClient } from './callers'; +export { default as manifestServer } from './handlers'; +export * as callers from './callers'; +export * as handlers from './handlers'; +export * as utils from './utils'; +export * as errors from './errors'; +export * as types from './types'; diff --git a/src/nodes/agent/types.ts b/src/nodes/agent/types.ts new file mode 100644 index 000000000..95de72950 --- /dev/null +++ b/src/nodes/agent/types.ts @@ -0,0 +1,79 @@ +import type { SignedTokenEncoded } from '../../tokens/types'; +import type { ClaimIdEncoded, NodeIdEncoded, VaultIdEncoded } from '../../ids'; +import type { VaultAction, VaultName } from '../../vaults/types'; +import type { SignedNotification } from '../../notifications/types'; +import type { JSONValue, ObjectEmpty } from '../../types'; + +// Prevent overwriting the metadata type with `Omit<>` +type AgentRPCRequestParams = ObjectEmpty> = + { + metadata?: { + [Key: string]: JSONValue; + } & Partial<{ + authorization: string; + timeout: number; + }>; + } & Omit; + +// Prevent overwriting the metadata type with `Omit<>` +type AgentRPCResponseResult = ObjectEmpty> = + { + metadata?: { + [Key: string]: JSONValue; + } & Partial<{ + authorization: string; + timeout: number; + }>; + } & Omit; + +type ClaimIdMessage = { + claimIdEncoded: ClaimIdEncoded; +}; + +type AgentClaimMessage = Partial & { + signedTokenEncoded: SignedTokenEncoded; +}; + +type NodeIdMessage = { + nodeIdEncoded: NodeIdEncoded; +}; + +type AddressMessage = { + host: string; + port: number; +}; + +type NodeAddressMessage = NodeIdMessage & AddressMessage; + +type HolePunchRelayMessage = { + srcIdEncoded: NodeIdEncoded; + dstIdEncoded: NodeIdEncoded; + address?: AddressMessage; +}; + +type SignedNotificationEncoded = { + signedNotificationEncoded: SignedNotification; +}; + +type VaultInfo = { + vaultIdEncoded: VaultIdEncoded; + vaultName: VaultName; +}; + +type VaultsScanMessage = VaultInfo & { + vaultPermissions: Array; +}; + +export type { + AgentRPCRequestParams, + AgentRPCResponseResult, + ClaimIdMessage, + AgentClaimMessage, + NodeIdMessage, + AddressMessage, + NodeAddressMessage, + HolePunchRelayMessage, + SignedNotificationEncoded, + VaultInfo, + VaultsScanMessage, +}; diff --git a/src/nodes/agent/utils.ts b/src/nodes/agent/utils.ts new file mode 100644 index 000000000..a12120c09 --- /dev/null +++ b/src/nodes/agent/utils.ts @@ -0,0 +1,25 @@ +import type { NodeId } from '../../ids/types'; +import type { CertificatePEM } from '../../keys/types'; +import { utils as quicUtils } from '@matrixai/quic'; +import * as keysUtils from '../../keys/utils'; + +/** + * Used to extract the NodeId from the connection metadata. + * Used by the RPC handlers when they need to know the NodeId of the requester. + * @param meta + */ +function nodeIdFromMeta(meta: any): NodeId | undefined { + const remoteCerts = meta.remoteCertsChain; + if (remoteCerts == null) return; + const leafCertDER = remoteCerts[0] as Uint8Array; + if (leafCertDER == null) return; + const leafCert = keysUtils.certFromPEM( + quicUtils.derToPEM(leafCertDER) as CertificatePEM, + ); + if (leafCert == null) return; + const nodeId = keysUtils.certNodeId(leafCert); + if (nodeId == null) return; + return nodeId; +} + +export { nodeIdFromMeta }; diff --git a/src/nodes/errors.ts b/src/nodes/errors.ts index 0f93bd108..faa22173d 100644 --- a/src/nodes/errors.ts +++ b/src/nodes/errors.ts @@ -1,90 +1,142 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; // TODO: Some errors may need to be removed here, TBD in stage 2 agent migration class ErrorNodes extends ErrorPolykey {} -class ErrorNodeAborted extends ErrorNodes { - static description = 'Operation was aborted'; - exitCode = sysexits.USAGE; -} +class ErrorNodeManager extends ErrorNodes {} -class ErrorNodeManagerNotRunning extends ErrorNodes { +class ErrorNodeManagerNotRunning extends ErrorNodeManager { static description = 'NodeManager is not running'; exitCode = sysexits.USAGE; } -class ErrorQueueNotRunning extends ErrorNodes { - static description = 'queue is not running'; - exitCode = sysexits.USAGE; -} +class ErrorNodeGraph extends ErrorNodes {} -class ErrorNodeGraphRunning extends ErrorNodes { +class ErrorNodeGraphRunning extends ErrorNodeGraph { static description = 'NodeGraph is running'; exitCode = sysexits.USAGE; } -class ErrorNodeGraphNotRunning extends ErrorNodes { +class ErrorNodeGraphNotRunning extends ErrorNodeGraph { static description = 'NodeGraph is not running'; exitCode = sysexits.USAGE; } -class ErrorNodeGraphDestroyed extends ErrorNodes { +class ErrorNodeGraphDestroyed extends ErrorNodeGraph { static description = 'NodeGraph is destroyed'; exitCode = sysexits.USAGE; } -class ErrorNodeGraphNodeIdNotFound extends ErrorNodes { +class ErrorNodeGraphNodeIdNotFound extends ErrorNodeGraph { static description = 'Could not find NodeId'; exitCode = sysexits.NOUSER; } -class ErrorNodeGraphOversizedBucket extends ErrorNodes { +class ErrorNodeGraphOversizedBucket extends ErrorNodeGraph { static description: 'Bucket invalidly contains more nodes than capacity'; exitCode = sysexits.USAGE; } -class ErrorNodeGraphSameNodeId extends ErrorNodes { +class ErrorNodeGraphSameNodeId extends ErrorNodeGraph { static description: 'NodeId must be different for valid bucket calculation'; exitCode = sysexits.USAGE; } -class ErrorNodeGraphBucketIndex extends ErrorNodes { +class ErrorNodeGraphBucketIndex extends ErrorNodeGraph { static description: 'Bucket index is out of range'; exitCode = sysexits.USAGE; } -class ErrorNodeConnectionDestroyed extends ErrorNodes { +class ErrorNodeConnection extends ErrorNodes {} + +class ErrorNodeConnectionDestroyed extends ErrorNodeConnection { static description = 'NodeConnection is destroyed'; exitCode = sysexits.USAGE; } -class ErrorNodeConnectionTimeout extends ErrorNodes { +class ErrorNodeConnectionTimeout extends ErrorNodeConnection { static description: 'A node connection could not be established (timed out)'; exitCode = sysexits.UNAVAILABLE; } -class ErrorNodeConnectionMultiConnectionFailed extends ErrorNodes { +class ErrorNodeConnectionMultiConnectionFailed< + T, +> extends ErrorNodeConnection { static description: 'Could not establish connection when multiple resolved hosts were involved'; exitCode = sysexits.UNAVAILABLE; } -class ErrorNodeConnectionManagerNotRunning extends ErrorNodes { +class ErrorNodeConnectionHostWildcard extends ErrorNodeConnection { + static description = 'An IP wildcard was provided for the target host'; + exitCode = sysexits.USAGE; +} + +class ErrorNodeConnectionSameNodeId extends ErrorNodeConnection { + static description = + 'Provided NodeId is the same as this agent, attempts to connect is improper usage'; + exitCode = sysexits.USAGE; +} +class ErrorNodeConnectionInternalError extends ErrorNodeConnection { + static description = 'There was an internal failure with the NodeConnection'; + exitCode = sysexits.UNAVAILABLE; +} + +class ErrorNodeConnectionTransportUnknownError< + T, +> extends ErrorNodeConnection { + static description = 'Transport received an unknown error'; + exitCode = sysexits.USAGE; +} + +class ErrorNodeConnectionTransportGenericError< + T, +> extends ErrorNodeConnection { + static description = 'Transport received a generic error'; + exitCode = sysexits.USAGE; +} + +class ErrorNodeConnectionManager extends ErrorNodes {} + +class ErrorNodeConnectionManagerNotRunning< + T, +> extends ErrorNodeConnectionManager { static description = 'NodeConnectionManager is not running'; exitCode = sysexits.USAGE; } -class ErrorNodeConnectionHostWildcard extends ErrorNodes { - static description = 'An IP wildcard was provided for the target host'; +class ErrorNodeConnectionManagerStopping< + T, +> extends ErrorNodeConnectionManager { + static description = 'NodeConnectionManager is stopping'; exitCode = sysexits.USAGE; } -class ErrorNodeConnectionSameNodeId extends ErrorNodes { +class ErrorNodeConnectionManagerInternalError< + T, +> extends ErrorNodeConnectionManager { static description = - 'Provided NodeId is the same as this agent, attempts to connect is improper usage'; + 'There was an internal failure with NodeConnectionManager'; + exitCode = sysexits.UNAVAILABLE; +} + +class ErrorNodeConnectionManagerNodeIdRequired< + T, +> extends ErrorNodeConnectionManager { + static description = + 'No NodeId was provided for establishing a multi connection'; exitCode = sysexits.USAGE; } +class ErrorNodeConnectionManagerMultiConnectionFailed< + T, +> extends ErrorNodeConnectionManager { + static description = + 'Failed to establish any connection during multi connection establishment'; + exitCode = sysexits.TEMPFAIL; +} + class ErrorNodePingFailed extends ErrorNodes { static description = 'Failed to ping the node when attempting to authenticate'; @@ -98,9 +150,9 @@ class ErrorNodePermissionDenied extends ErrorNodes { export { ErrorNodes, - ErrorNodeAborted, + ErrorNodeManager, ErrorNodeManagerNotRunning, - ErrorQueueNotRunning, + ErrorNodeGraph, ErrorNodeGraphRunning, ErrorNodeGraphNotRunning, ErrorNodeGraphDestroyed, @@ -108,12 +160,21 @@ export { ErrorNodeGraphOversizedBucket, ErrorNodeGraphSameNodeId, ErrorNodeGraphBucketIndex, + ErrorNodeConnection, ErrorNodeConnectionDestroyed, ErrorNodeConnectionTimeout, ErrorNodeConnectionMultiConnectionFailed, - ErrorNodeConnectionManagerNotRunning, ErrorNodeConnectionHostWildcard, ErrorNodeConnectionSameNodeId, + ErrorNodeConnectionInternalError, + ErrorNodeConnectionTransportUnknownError, + ErrorNodeConnectionTransportGenericError, + ErrorNodeConnectionManager, + ErrorNodeConnectionManagerNotRunning, + ErrorNodeConnectionManagerStopping, + ErrorNodeConnectionManagerInternalError, + ErrorNodeConnectionManagerNodeIdRequired, + ErrorNodeConnectionManagerMultiConnectionFailed, ErrorNodePingFailed, ErrorNodePermissionDenied, }; diff --git a/src/nodes/events.ts b/src/nodes/events.ts index b110c09f9..1a412fe04 100644 --- a/src/nodes/events.ts +++ b/src/nodes/events.ts @@ -1,7 +1,92 @@ -class NodeConnectionDestroyEvent extends Event { - constructor(options?: EventInit) { - super('destroy', options); - } -} +import type { QUICStream } from '@matrixai/quic'; +import type { ConnectionData } from '../network/types'; +import EventPolykey from '../EventPolykey'; -export { NodeConnectionDestroyEvent }; +abstract class EventNode extends EventPolykey {} + +abstract class EventNodeConnection extends EventNode {} + +class EventNodeConnectionError extends EventNodeConnection {} + +class EventNodeConnectionClose extends EventNodeConnection {} + +class EventNodeConnectionDestroy extends EventNodeConnection {} + +class EventNodeConnectionDestroyed extends EventNodeConnection {} + +class EventNodeConnectionStream extends EventNode {} + +abstract class EventNodeConnectionManager extends EventNode {} + +class EventNodeConnectionManagerStart extends EventNodeConnectionManager {} + +class EventNodeConnectionManagerStarted extends EventNodeConnectionManager {} + +class EventNodeConnectionManagerStop extends EventNodeConnectionManager {} + +class EventNodeConnectionManagerStopped extends EventNodeConnectionManager {} + +class EventNodeConnectionManagerError extends EventNodeConnectionManager {} + +class EventNodeConnectionManagerClose extends EventNodeConnectionManager {} + +class EventNodeConnectionManagerConnection extends EventNodeConnectionManager {} + +class EventNodeConnectionManagerConnectionFailure extends EventNodeConnectionManager< + Error | EventNodeConnectionError +> {} + +abstract class EventNodeGraph extends EventPolykey {} + +class EventNodeGraphStart extends EventNodeGraph {} + +class EventNodeGraphStarted extends EventNodeGraph {} + +class EventNodeGraphStop extends EventNodeGraph {} + +class EventNodeGraphStopped extends EventNodeGraph {} + +class EventNodeGraphDestroy extends EventNodeGraph {} + +class EventNodeGraphDestroyed extends EventNodeGraph {} + +abstract class EventNodeManager extends EventPolykey {} + +class EventNodeManagerStart extends EventNodeManager {} + +class EventNodeManagerStarted extends EventNodeManager {} + +class EventNodeManagerStop extends EventNodeManager {} + +class EventNodeManagerStopped extends EventNodeManager {} + +export { + EventNode, + EventNodeConnection, + EventNodeConnectionError, + EventNodeConnectionClose, + EventNodeConnectionDestroy, + EventNodeConnectionDestroyed, + EventNodeConnectionStream, + EventNodeConnectionManager, + EventNodeConnectionManagerStart, + EventNodeConnectionManagerStarted, + EventNodeConnectionManagerStop, + EventNodeConnectionManagerStopped, + EventNodeConnectionManagerError, + EventNodeConnectionManagerClose, + EventNodeConnectionManagerConnection, + EventNodeConnectionManagerConnectionFailure, + EventNodeGraph, + EventNodeGraphStart, + EventNodeGraphStarted, + EventNodeGraphStop, + EventNodeGraphStopped, + EventNodeGraphDestroy, + EventNodeGraphDestroyed, + EventNodeManager, + EventNodeManagerStart, + EventNodeManagerStarted, + EventNodeManagerStop, + EventNodeManagerStopped, +}; diff --git a/src/nodes/index.ts b/src/nodes/index.ts index 93d06056f..09265ea67 100644 --- a/src/nodes/index.ts +++ b/src/nodes/index.ts @@ -2,6 +2,8 @@ export { default as NodeManager } from './NodeManager'; export { default as NodeGraph } from './NodeGraph'; export { default as NodeConnectionManager } from './NodeConnectionManager'; export { default as NodeConnection } from './NodeConnection'; +export * as agent from './agent'; +export * as utils from './utils'; +export * as events from './events'; export * as errors from './errors'; export * as types from './types'; -export * as utils from './utils'; diff --git a/src/nodes/types.ts b/src/nodes/types.ts index 41eb082b3..f52cd7aac 100644 --- a/src/nodes/types.ts +++ b/src/nodes/types.ts @@ -26,6 +26,17 @@ type NodeData = { type SeedNodes = Record; +type NodesOptions = { + connectionIdleTimeoutTime: number; + connectionFindConcurrencyLimit: number; + connectionConnectTimeoutTime: number; + connectionKeepAliveTimeoutTime: number; + connectionKeepAliveIntervalTime: number; + connectionHolePunchIntervalTime: number; + rpcParserBufferSize: number; + rpcCallTimeoutTime: number; +}; + export type { NodeId, NodeIdString, @@ -37,4 +48,5 @@ export type { NodeBucket, NodeData, NodeGraphSpace, + NodesOptions, }; diff --git a/src/nodes/utils.ts b/src/nodes/utils.ts index 47fc72e91..97f6f3580 100644 --- a/src/nodes/utils.ts +++ b/src/nodes/utils.ts @@ -1,12 +1,15 @@ import type { NodeBucket, NodeBucketIndex, NodeId } from './types'; +import type { CertificatePEM } from '../keys/types'; import type { KeyPath } from '@matrixai/db'; import { utils as dbUtils } from '@matrixai/db'; import { IdInternal } from '@matrixai/id'; import lexi from 'lexicographic-integer'; +import { utils as quicUtils } from '@matrixai/quic'; import * as nodesErrors from './errors'; import * as keysUtils from '../keys/utils'; import { encodeNodeId, decodeNodeId } from '../ids'; -import { bytes2BigInt } from '../utils'; +import { bytes2BigInt, never } from '../utils'; +import * as rpcErrors from '../rpc/errors'; const sepBuffer = dbUtils.sep; @@ -313,6 +316,74 @@ function refreshBucketsDelayJitter( return (Math.random() - 0.5) * delay * jitterMultiplier; } +/** + * Converts transport level error reasons to codes for the quic system. + * + * Any unknown reasons default to code 0. + * + */ +const reasonToCode = (_type: 'read' | 'write', reason?: any): number => { + // We're only going to handle RPC errors for now, these include + // ErrorRPCHandlerFailed + // ErrorRPCMessageLength + // ErrorRPCMissingResponse + // ErrorRPCOutputStreamError + // ErrorPolykeyRemote + // ErrorRPCStreamEnded + // ErrorRPCTimedOut + if (reason instanceof rpcErrors.ErrorRPCHandlerFailed) return 1; + if (reason instanceof rpcErrors.ErrorRPCMessageLength) return 2; + if (reason instanceof rpcErrors.ErrorRPCMissingResponse) return 3; + if (reason instanceof rpcErrors.ErrorRPCOutputStreamError) return 4; + if (reason instanceof rpcErrors.ErrorPolykeyRemote) return 5; + if (reason instanceof rpcErrors.ErrorRPCStreamEnded) return 6; + if (reason instanceof rpcErrors.ErrorRPCTimedOut) return 7; + return 0; +}; + +/** + * Converts any codes from the quic system back to reasons + * @param _type + * @param code + */ +const codeToReason = (_type: 'read' | 'write', code: number): any => { + switch (code) { + // Rpc errors + case 1: + return new rpcErrors.ErrorRPCHandlerFailed(); + case 2: + return new rpcErrors.ErrorRPCMessageLength(); + case 3: + return new rpcErrors.ErrorRPCMissingResponse(); + case 4: + return new rpcErrors.ErrorRPCOutputStreamError(); + case 5: + return new rpcErrors.ErrorPolykeyRemote(); + case 6: + return new rpcErrors.ErrorRPCStreamEnded(); + case 7: + return new rpcErrors.ErrorRPCTimedOut(); + // Base cases + case 0: + return new nodesErrors.ErrorNodeConnectionTransportGenericError(); + default: + return new nodesErrors.ErrorNodeConnectionTransportUnknownError(); + } +}; + +function parseRemoteCertsChain(remoteCertChain: Array) { + const certChain = remoteCertChain.map((der) => { + const cert = keysUtils.certFromPEM( + quicUtils.derToPEM(der) as CertificatePEM, + ); + if (cert == null) never(); + return cert; + }); + const nodeId = keysUtils.certNodeId(certChain[0]); + if (nodeId == null) never(); + return { nodeId, certChain }; +} + export { sepBuffer, encodeNodeId, @@ -335,4 +406,7 @@ export { generateRandomNodeIdForBucket, isConnectionError, refreshBucketsDelayJitter, + reasonToCode, + codeToReason, + parseRemoteCertsChain, }; diff --git a/src/notifications/NotificationsManager.ts b/src/notifications/NotificationsManager.ts index 64a761f21..92e1da96a 100644 --- a/src/notifications/NotificationsManager.ts +++ b/src/notifications/NotificationsManager.ts @@ -14,6 +14,7 @@ import { import { utils as idUtils } from '@matrixai/id'; import * as notificationsUtils from './utils'; import * as notificationsErrors from './errors'; +import * as notificationsEvents from './events'; import * as nodesUtils from '../nodes/utils'; import { never } from '../utils/utils'; @@ -26,6 +27,14 @@ interface NotificationsManager extends CreateDestroyStartStop {} @CreateDestroyStartStop( new notificationsErrors.ErrorNotificationsRunning(), new notificationsErrors.ErrorNotificationsDestroyed(), + { + eventStart: notificationsEvents.EventNotificationsManagerStart, + eventStarted: notificationsEvents.EventNotificationsManagerStarted, + eventStop: notificationsEvents.EventNotificationsManagerStop, + eventStopped: notificationsEvents.EventNotificationsManagerStopped, + eventDestroy: notificationsEvents.EventNotificationsManagerDestroy, + eventDestroyed: notificationsEvents.EventNotificationsManagerDestroyed, + }, ) class NotificationsManager { static async createNotificationsManager({ diff --git a/src/notifications/errors.ts b/src/notifications/errors.ts index 266e3b2e6..be9b40243 100644 --- a/src/notifications/errors.ts +++ b/src/notifications/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorNotifications extends ErrorPolykey {} diff --git a/src/notifications/events.ts b/src/notifications/events.ts new file mode 100644 index 000000000..3e36bf1fc --- /dev/null +++ b/src/notifications/events.ts @@ -0,0 +1,25 @@ +import EventPolykey from '../EventPolykey'; + +abstract class EventNotificationsManager extends EventPolykey {} + +class EventNotificationsManagerStart extends EventNotificationsManager {} + +class EventNotificationsManagerStarted extends EventNotificationsManager {} + +class EventNotificationsManagerStop extends EventNotificationsManager {} + +class EventNotificationsManagerStopped extends EventNotificationsManager {} + +class EventNotificationsManagerDestroy extends EventNotificationsManager {} + +class EventNotificationsManagerDestroyed extends EventNotificationsManager {} + +export { + EventNotificationsManager, + EventNotificationsManagerStart, + EventNotificationsManagerStarted, + EventNotificationsManagerStop, + EventNotificationsManagerStopped, + EventNotificationsManagerDestroy, + EventNotificationsManagerDestroyed, +}; diff --git a/src/notifications/index.ts b/src/notifications/index.ts index 8e47bbfb3..9fd78f3d2 100644 --- a/src/notifications/index.ts +++ b/src/notifications/index.ts @@ -2,3 +2,4 @@ export { default as NotificationsManager } from './NotificationsManager'; export * as utils from './utils'; export * as types from './types'; export * as errors from './errors'; +export * as events from './events'; diff --git a/src/rpc/RPCServer.ts b/src/rpc/RPCServer.ts index bf300ed4e..3f6ebe0e8 100644 --- a/src/rpc/RPCServer.ts +++ b/src/rpc/RPCServer.ts @@ -44,7 +44,9 @@ const cleanupReason = Symbol('CleanupReason'); * - error */ interface RPCServer extends CreateDestroy {} -@CreateDestroy() +@CreateDestroy({ + eventDestroyed: rpcEvents.EventRPCServerDestroyed, +}) class RPCServer extends EventTarget { /** * Creates RPC server. @@ -194,7 +196,12 @@ class RPCServer extends EventTarget { this.logger = logger; } - public async destroy(force: boolean = true): Promise { + public async destroy({ + force = true, + reason, + }: { force?: boolean; reason?: any } = {}): Promise { + // Just adding this to enforce expected type + const _reason = reason; this.logger.info(`Destroying ${this.constructor.name}`); // Stopping any active steams if (force) { diff --git a/src/rpc/errors.ts b/src/rpc/errors.ts index 455197aa1..ed3f1be2b 100644 --- a/src/rpc/errors.ts +++ b/src/rpc/errors.ts @@ -2,7 +2,8 @@ import type { Class } from '@matrixai/errors'; import type { ClientMetadata } from './types'; import type { JSONValue } from '../types'; import * as nodesUtils from '../nodes/utils'; -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorRPC extends ErrorPolykey {} diff --git a/src/rpc/events.ts b/src/rpc/events.ts index 210d67691..340618991 100644 --- a/src/rpc/events.ts +++ b/src/rpc/events.ts @@ -1,3 +1,5 @@ +import { AbstractEvent } from '@matrixai/events'; + class RPCErrorEvent extends Event { public detail: Error; constructor( @@ -10,4 +12,6 @@ class RPCErrorEvent extends Event { } } -export { RPCErrorEvent }; +class EventRPCServerDestroyed extends AbstractEvent {} + +export { RPCErrorEvent, EventRPCServerDestroyed }; diff --git a/src/rpc/types.ts b/src/rpc/types.ts index 574d3045d..d339acd0c 100644 --- a/src/rpc/types.ts +++ b/src/rpc/types.ts @@ -195,11 +195,8 @@ type ContainerType = Record; * mainly used as the return type for the `StreamFactory`. But the interface * can be propagated across the RPC system. */ -interface RPCStream< - R, - W, - M extends Record = Record, -> extends ReadableWritablePair { +interface RPCStream + extends ReadableWritablePair { cancel: (reason?: any) => void; meta?: M; } @@ -312,6 +309,18 @@ type ClientManifest = Record; type HandlerType = 'DUPLEX' | 'SERVER' | 'CLIENT' | 'UNARY' | 'RAW'; +type HandlerTypes = T extends Handler< + infer Container, + infer Input, + infer Output +> + ? { + container: Container; + input: Input; + output: Output; + } + : never; + type MapCallers = { [K in keyof T]: ConvertCaller; }; @@ -345,6 +354,7 @@ export type { ServerManifest, ClientManifest, HandlerType, + HandlerTypes, MapCallers, ClientMetadata, }; diff --git a/src/schema/Schema.ts b/src/schema/Schema.ts index 0476c2e01..10ffaeff3 100644 --- a/src/schema/Schema.ts +++ b/src/schema/Schema.ts @@ -5,6 +5,7 @@ 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 schemaEvents from './events'; import * as utils from '../utils'; import config from '../config'; @@ -12,6 +13,12 @@ interface Schema extends CreateDestroyStartStop {} @CreateDestroyStartStop( new schemaErrors.ErrorSchemaRunning(), new schemaErrors.ErrorSchemaDestroyed(), + { + eventStart: schemaEvents.EventSchemaStart, + eventStarted: schemaEvents.EventSchemaStarted, + eventStop: schemaEvents.EventSchemaStop, + eventStopped: schemaEvents.EventSchemaStopped, + }, ) class Schema { public static async createSchema({ @@ -59,10 +66,7 @@ class Schema { }) { this.logger = logger ?? new Logger(this.constructor.name); this.statePath = statePath; - this.stateVersionPath = path.join( - statePath, - config.defaults.stateVersionBase, - ); + this.stateVersionPath = path.join(statePath, config.paths.stateVersionBase); this.stateVersion = stateVersion; this.fs = fs; } diff --git a/src/schema/errors.ts b/src/schema/errors.ts index 973961386..c1d9e9ca9 100644 --- a/src/schema/errors.ts +++ b/src/schema/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorSchema extends ErrorPolykey {} diff --git a/src/schema/events.ts b/src/schema/events.ts new file mode 100644 index 000000000..7e04ee914 --- /dev/null +++ b/src/schema/events.ts @@ -0,0 +1,19 @@ +import EventPolykey from '../EventPolykey'; + +abstract class EventSchema extends EventPolykey {} + +class EventSchemaStart extends EventSchema {} + +class EventSchemaStarted extends EventSchema {} + +class EventSchemaStop extends EventSchema {} + +class EventSchemaStopped extends EventSchema {} + +export { + EventSchema, + EventSchemaStart, + EventSchemaStarted, + EventSchemaStop, + EventSchemaStopped, +}; diff --git a/src/schema/index.ts b/src/schema/index.ts index 04cd2de4d..a76e576c2 100644 --- a/src/schema/index.ts +++ b/src/schema/index.ts @@ -1,3 +1,4 @@ export { default as Schema } from './Schema'; export * as errors from './errors'; export * as types from './types'; +export * as events from './events'; diff --git a/src/sessions/Session.ts b/src/sessions/Session.ts index 8f4bf2c8f..c185945f9 100644 --- a/src/sessions/Session.ts +++ b/src/sessions/Session.ts @@ -4,12 +4,21 @@ import Logger from '@matrixai/logger'; import { CreateDestroyStartStop } from '@matrixai/async-init/dist/CreateDestroyStartStop'; import lock from 'fd-lock'; import * as sessionErrors from './errors'; +import * as events from './events'; import * as utils from '../utils'; interface Session extends CreateDestroyStartStop {} @CreateDestroyStartStop( new sessionErrors.ErrorSessionRunning(), new sessionErrors.ErrorSessionDestroyed(), + { + eventStart: events.EventSessionStart, + eventStarted: events.EventSessionStarted, + eventStop: events.EventSessionStop, + eventStopped: events.EventSessionStopped, + eventDestroy: events.EventSessionDestroy, + eventDestroyed: events.EventSessionDestroyed, + }, ) class Session { static async createSession({ diff --git a/src/sessions/SessionManager.ts b/src/sessions/SessionManager.ts index f1de6c611..59ec967aa 100644 --- a/src/sessions/SessionManager.ts +++ b/src/sessions/SessionManager.ts @@ -10,6 +10,7 @@ import { import { withF } from '@matrixai/resources'; import * as sessionsUtils from './utils'; import * as sessionsErrors from './errors'; +import * as sessionsEvents from './events'; import * as keysUtils from '../keys/utils'; import * as nodesUtils from '../nodes/utils'; @@ -17,6 +18,14 @@ interface SessionManager extends CreateDestroyStartStop {} @CreateDestroyStartStop( new sessionsErrors.ErrorSessionManagerRunning(), new sessionsErrors.ErrorSessionManagerDestroyed(), + { + eventStart: sessionsEvents.EventSessionManagerStart, + eventStarted: sessionsEvents.EventSessionManagerStarted, + eventStop: sessionsEvents.EventSessionManagerStop, + eventStopped: sessionsEvents.EventSessionManagerStopped, + eventDestroy: sessionsEvents.EventSessionManagerDestroy, + eventDestroyed: sessionsEvents.EventSessionManagerDestroyed, + }, ) class SessionManager { static async createSessionManager({ diff --git a/src/sessions/errors.ts b/src/sessions/errors.ts index 588ff9036..a09d06a36 100644 --- a/src/sessions/errors.ts +++ b/src/sessions/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorSessions extends ErrorPolykey {} diff --git a/src/sessions/events.ts b/src/sessions/events.ts new file mode 100644 index 000000000..f2625cd6b --- /dev/null +++ b/src/sessions/events.ts @@ -0,0 +1,49 @@ +import EventPolykey from '../EventPolykey'; + +abstract class EventSessions extends EventPolykey {} + +abstract class EventSession extends EventSessions {} + +class EventSessionStart extends EventSession {} + +class EventSessionStarted extends EventSession {} + +class EventSessionStop extends EventSession {} + +class EventSessionStopped extends EventSession {} + +class EventSessionDestroy extends EventSession {} + +class EventSessionDestroyed extends EventSession {} + +abstract class EventSessionManager extends EventSessions {} + +class EventSessionManagerStart extends EventSessionManager {} + +class EventSessionManagerStarted extends EventSessionManager {} + +class EventSessionManagerStop extends EventSessionManager {} + +class EventSessionManagerStopped extends EventSessionManager {} + +class EventSessionManagerDestroy extends EventSessionManager {} + +class EventSessionManagerDestroyed extends EventSessionManager {} + +export { + EventSessions, + EventSession, + EventSessionStart, + EventSessionStarted, + EventSessionStop, + EventSessionStopped, + EventSessionDestroy, + EventSessionDestroyed, + EventSessionManager, + EventSessionManagerStart, + EventSessionManagerStarted, + EventSessionManagerStop, + EventSessionManagerStopped, + EventSessionManagerDestroy, + EventSessionManagerDestroyed, +}; diff --git a/src/sessions/index.ts b/src/sessions/index.ts index 023980699..e027a4bef 100644 --- a/src/sessions/index.ts +++ b/src/sessions/index.ts @@ -3,3 +3,4 @@ export { default as Session } from './Session'; export * as errors from './errors'; export * as types from './types'; export * as utils from './utils'; +export * as events from './events'; diff --git a/src/sigchain/Sigchain.ts b/src/sigchain/Sigchain.ts index 7b920d530..8e3e020f5 100644 --- a/src/sigchain/Sigchain.ts +++ b/src/sigchain/Sigchain.ts @@ -15,6 +15,7 @@ import { ready, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; import * as sigchainErrors from './errors'; +import * as sigchainEvents from './events'; import Token from '../tokens/Token'; import * as claimsUtils from '../claims/utils'; import * as utils from '../utils'; @@ -23,6 +24,14 @@ interface Sigchain extends CreateDestroyStartStop {} @CreateDestroyStartStop( new sigchainErrors.ErrorSigchainRunning(), new sigchainErrors.ErrorSigchainDestroyed(), + { + eventStart: sigchainEvents.EventSigchainStart, + eventStarted: sigchainEvents.EventSigchainStarted, + eventStop: sigchainEvents.EventSigchainStop, + eventStopped: sigchainEvents.EventSigchainStopped, + eventDestroy: sigchainEvents.EventSigchainDestroy, + eventDestroyed: sigchainEvents.EventSigchainDestroyed, + }, ) class Sigchain { public static async createSigchain({ diff --git a/src/sigchain/errors.ts b/src/sigchain/errors.ts index f1d1943ff..2277bc93b 100644 --- a/src/sigchain/errors.ts +++ b/src/sigchain/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorSigchain extends ErrorPolykey {} diff --git a/src/sigchain/events.ts b/src/sigchain/events.ts new file mode 100644 index 000000000..55ed4e27f --- /dev/null +++ b/src/sigchain/events.ts @@ -0,0 +1,25 @@ +import EventPolykey from '../EventPolykey'; + +abstract class EventSigchain extends EventPolykey {} + +class EventSigchainStart extends EventSigchain {} + +class EventSigchainStarted extends EventSigchain {} + +class EventSigchainStop extends EventSigchain {} + +class EventSigchainStopped extends EventSigchain {} + +class EventSigchainDestroy extends EventSigchain {} + +class EventSigchainDestroyed extends EventSigchain {} + +export { + EventSigchain, + EventSigchainStart, + EventSigchainStarted, + EventSigchainStop, + EventSigchainStopped, + EventSigchainDestroy, + EventSigchainDestroyed, +}; diff --git a/src/sigchain/index.ts b/src/sigchain/index.ts index e368889a3..5a0a2f74f 100644 --- a/src/sigchain/index.ts +++ b/src/sigchain/index.ts @@ -1,3 +1,4 @@ export { default as Sigchain } from './Sigchain'; export * as types from './types'; export * as errors from './errors'; +export * as events from './events'; diff --git a/src/status/Status.ts b/src/status/Status.ts index 01647736c..eac8ecba7 100644 --- a/src/status/Status.ts +++ b/src/status/Status.ts @@ -9,14 +9,20 @@ import type { FileSystem, FileHandle } from '../types'; import Logger from '@matrixai/logger'; import lock from 'fd-lock'; import { StartStop, ready } from '@matrixai/async-init/dist/StartStop'; -import * as statusErrors from './errors'; import * as statusUtils from './utils'; +import * as statusErrors from './errors'; +import * as statusEvents from './events'; import { sleep, poll } from '../utils'; import * as errors from '../errors'; import { utils as nodesUtils } from '../nodes'; interface Status extends StartStop {} -@StartStop() +@StartStop({ + eventStart: statusEvents.EventStatusStart, + eventStarted: statusEvents.EventStatusStarted, + eventStop: statusEvents.EventStatusStop, + eventStopped: statusEvents.EventStatusStopped, +}) class Status { public readonly statusPath: string; public readonly statusLockPath: string; diff --git a/src/status/errors.ts b/src/status/errors.ts index a56192557..746f493b6 100644 --- a/src/status/errors.ts +++ b/src/status/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorStatus extends ErrorPolykey {} diff --git a/src/status/events.ts b/src/status/events.ts new file mode 100644 index 000000000..ec36ba92b --- /dev/null +++ b/src/status/events.ts @@ -0,0 +1,19 @@ +import EventPolykey from '../EventPolykey'; + +abstract class EventStatus extends EventPolykey {} + +class EventStatusStart extends EventStatus {} + +class EventStatusStarted extends EventStatus {} + +class EventStatusStop extends EventStatus {} + +class EventStatusStopped extends EventStatus {} + +export { + EventStatus, + EventStatusStart, + EventStatusStarted, + EventStatusStop, + EventStatusStopped, +}; diff --git a/src/status/index.ts b/src/status/index.ts index d80ffade1..9797c9593 100644 --- a/src/status/index.ts +++ b/src/status/index.ts @@ -2,3 +2,4 @@ export { default as Status } from './Status'; export * as types from './types'; export * as errors from './errors'; export * as utils from './utils'; +export * as events from './events'; diff --git a/src/tasks/TaskManager.ts b/src/tasks/TaskManager.ts index 369fbb31b..37418e607 100644 --- a/src/tasks/TaskManager.ts +++ b/src/tasks/TaskManager.ts @@ -24,8 +24,9 @@ import { PromiseCancellable } from '@matrixai/async-cancellable'; import { extractTs } from '@matrixai/id/dist/IdSortable'; import { Timer } from '@matrixai/timer'; import TaskEvent from './TaskEvent'; -import * as tasksErrors from './errors'; import * as tasksUtils from './utils'; +import * as tasksErrors from './errors'; +import * as tasksEvents from './events'; import * as utils from '../utils'; const abortSchedulingLoopReason = Symbol('abort scheduling loop reason'); @@ -35,6 +36,14 @@ interface TaskManager extends CreateDestroyStartStop {} @CreateDestroyStartStop( new tasksErrors.ErrorTaskManagerRunning(), new tasksErrors.ErrorTaskManagerDestroyed(), + { + eventStart: tasksEvents.EventTaskManagerStart, + eventStarted: tasksEvents.EventTaskManagerStarted, + eventStop: tasksEvents.EventTaskManagerStop, + eventStopped: tasksEvents.EventTaskManagerStopped, + eventDestroy: tasksEvents.EventTaskManagerDestroy, + eventDestroyed: tasksEvents.EventTaskManagerDestroyed, + }, ) class TaskManager { public static async createTaskManager({ diff --git a/src/tasks/errors.ts b/src/tasks/errors.ts index 61645f5a3..3e011a7fb 100644 --- a/src/tasks/errors.ts +++ b/src/tasks/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorTasks extends ErrorPolykey {} diff --git a/src/tasks/events.ts b/src/tasks/events.ts new file mode 100644 index 000000000..7658fc105 --- /dev/null +++ b/src/tasks/events.ts @@ -0,0 +1,28 @@ +import EventPolykey from '../EventPolykey'; + +abstract class EventTasks extends EventPolykey {} + +abstract class EventTaskManager extends EventTasks {} + +class EventTaskManagerStart extends EventTaskManager {} + +class EventTaskManagerStarted extends EventTaskManager {} + +class EventTaskManagerStop extends EventTaskManager {} + +class EventTaskManagerStopped extends EventTaskManager {} + +class EventTaskManagerDestroy extends EventTaskManager {} + +class EventTaskManagerDestroyed extends EventTaskManager {} + +export { + EventTasks, + EventTaskManager, + EventTaskManagerStart, + EventTaskManagerStarted, + EventTaskManagerStop, + EventTaskManagerStopped, + EventTaskManagerDestroy, + EventTaskManagerDestroyed, +}; diff --git a/src/tasks/index.ts b/src/tasks/index.ts index 11ffc0c80..78fb255da 100644 --- a/src/tasks/index.ts +++ b/src/tasks/index.ts @@ -2,3 +2,4 @@ export { default as TaskManager } from './TaskManager'; export * as types from './types'; export * as utils from './utils'; export * as errors from './errors'; +export * as events from './events'; diff --git a/src/tokens/errors.ts b/src/tokens/errors.ts index b4d105ff8..857854357 100644 --- a/src/tokens/errors.ts +++ b/src/tokens/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorTokens extends ErrorPolykey {} diff --git a/src/types.ts b/src/types.ts index ea789c1c7..810a35352 100644 --- a/src/types.ts +++ b/src/types.ts @@ -65,6 +65,15 @@ interface ToString { */ type DeepReadonly = { readonly [K in keyof T]: DeepReadonly }; +/** + * Recursive partial + */ +type DeepPartial = T extends object + ? { + [P in keyof T]?: DeepPartial; + } + : T; + /** * Wrap a type to be reference counted * Useful for when we need to garbage collect data @@ -169,6 +178,7 @@ export type { InitialParameters, ToString, DeepReadonly, + DeepPartial, Ref, Timer, PromiseDeconstructed, diff --git a/src/utils/utils.ts b/src/utils/utils.ts index eda95679a..1eda5a7b7 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,5 @@ import type { + POJO, FileSystem, Timer, PromiseDeconstructed, @@ -43,8 +44,8 @@ function getDefaultNodePath(): string | undefined { return p; } -function never(): never { - throw new utilsErrors.ErrorUtilsUndefinedBehaviour(); +function never(message?: string): never { + throw new utilsErrors.ErrorUtilsUndefinedBehaviour(message); } async function mkdirExists(fs: FileSystem, path, ...args) { @@ -111,11 +112,35 @@ function isEmptyObject(o) { * Filters out all undefined properties recursively */ function filterEmptyObject(o) { - return Object.fromEntries( - Object.entries(o) - .filter(([_, v]) => v !== undefined) - .map(([k, v]) => [k, v === Object(v) ? filterEmptyObject(v) : v]), - ); + return filterObject(o, ([_, v]) => v !== undefined).map(([k, v]) => [ + k, + v === Object(v) ? filterEmptyObject(v) : v, + ]); +} + +function filterObject, K extends string, V>( + obj: T, + f: (element: [K, V], index: number, arr: Array<[K, V]>) => boolean, +): Partial { + return Object.fromEntries(Object.entries(obj).filter(f)) as Partial; +} + +/** + * Merges an input object to a default object. + */ +function mergeObjects(object1: POJO, object2: POJO): POJO { + const keys = new Set([...Object.keys(object2), ...Object.keys(object1)]); + const mergedObject = {}; + for (const key of keys) { + if (isObject(object1[key]) && isObject(object2[key])) { + mergedObject[key] = mergeObjects(object1[key], object2[key]); + } else if (object1[key] !== undefined) { + mergedObject[key] = object1[key]; + } else { + mergedObject[key] = object2[key]; + } + } + return mergedObject; } function getUnixtime(date: Date = new Date()) { @@ -429,22 +454,6 @@ function lexiUnpackBuffer(b: Buffer): number { return lexi.unpack([...b]); } -// TODO: remove this, quick hack to allow errors to jump the network -const codeMap = new Map(); -let code = 1; - -const reasonToCode = (_type: 'recv' | 'send', _reason?: any): number => { - codeMap.set(code, _reason); - const returnCode = code; - code++; - return returnCode; -}; - -const codeToReason = (type: 'recv' | 'send', code: number): any => { - const asd = codeMap.get(code); - return asd; -}; - export { AsyncFunction, GeneratorFunction, @@ -458,6 +467,8 @@ export { isObject, isEmptyObject, filterEmptyObject, + filterObject, + mergeObjects, getUnixtime, poll, promisify, @@ -480,6 +491,4 @@ export { lexiUnpackBuffer, bufferWrap, isBufferSource, - reasonToCode, - codeToReason, }; diff --git a/src/validation/errors.ts b/src/validation/errors.ts index 83e4ead93..498f41e14 100644 --- a/src/validation/errors.ts +++ b/src/validation/errors.ts @@ -1,5 +1,6 @@ import { AbstractError } from '@matrixai/errors'; -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; /** * Generic error containing all parsing errors that occurred during diff --git a/src/validation/utils.ts b/src/validation/utils.ts index 270fa44ca..cb5b3166d 100644 --- a/src/validation/utils.ts +++ b/src/validation/utils.ts @@ -234,14 +234,12 @@ function parsePort(data: any, connect: boolean = false): Port { } function parseNetwork(data: any): SeedNodes { - if (typeof data !== 'string' || !(data in config.defaults.network)) { + if (typeof data !== 'string' || !(data in config.network)) { throw new validationErrors.ErrorParse( - `Network must be one of ${Object.keys(config.defaults.network).join( - ', ', - )}`, + `Network must be one of ${Object.keys(config.network).join(', ')}`, ); } - return config.defaults.network[data]; + return config.network[data]; } /** diff --git a/src/vaults/VaultInternal.ts b/src/vaults/VaultInternal.ts index 34caefcc0..aae9819c1 100644 --- a/src/vaults/VaultInternal.ts +++ b/src/vaults/VaultInternal.ts @@ -16,7 +16,7 @@ import type KeyRing from '../keys/KeyRing'; import type { NodeId, NodeIdEncoded } from '../ids/types'; import type NodeConnectionManager from '../nodes/NodeConnectionManager'; import type RPCClient from '../rpc/RPCClient'; -import type { clientManifest as agentClientManifest } from '../agent/handlers/clientManifest'; +import type agentClientManifest from '../nodes/agent/callers'; import type { POJO } from '../types'; import path from 'path'; import git from 'isomorphic-git'; @@ -27,8 +27,9 @@ import { } 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 * as vaultsErrors from './errors'; +import * as vaultsEvents from './events'; import { tagLast } from './types'; import * as validationUtils from '../validation/utils'; import * as utils from '../utils'; @@ -44,6 +45,14 @@ interface VaultInternal extends CreateDestroyStartStop {} @CreateDestroyStartStop( new vaultsErrors.ErrorVaultRunning(), new vaultsErrors.ErrorVaultDestroyed(), + { + eventStart: vaultsEvents.EventVaultInternalStart, + eventStarted: vaultsEvents.EventVaultInternalStarted, + eventStop: vaultsEvents.EventVaultInternalStop, + eventStopped: vaultsEvents.EventVaultInternalStopped, + eventDestroy: vaultsEvents.EventVaultInternalDestroy, + eventDestroyed: vaultsEvents.EventVaultInternalDestroyed, + }, ) class VaultInternal { public static async createVaultInternal({ diff --git a/src/vaults/VaultManager.ts b/src/vaults/VaultManager.ts index e98dc89c9..f9996216b 100644 --- a/src/vaults/VaultManager.ts +++ b/src/vaults/VaultManager.ts @@ -32,6 +32,7 @@ import { IdInternal } from '@matrixai/id'; import { withF, withG } from '@matrixai/resources'; import { LockBox, RWLockWriter } from '@matrixai/async-locks'; import VaultInternal from './VaultInternal'; +import * as vaultsEvents from './events'; import * as utils from '../utils'; import * as vaultsUtils from '../vaults/utils'; import * as vaultsErrors from '../vaults/errors'; @@ -58,6 +59,14 @@ interface VaultManager extends CreateDestroyStartStop {} @CreateDestroyStartStop( new vaultsErrors.ErrorVaultManagerRunning(), new vaultsErrors.ErrorVaultManagerDestroyed(), + { + eventStart: vaultsEvents.EventVaultManagerStart, + eventStarted: vaultsEvents.EventVaultManagerStarted, + eventStop: vaultsEvents.EventVaultManagerStop, + eventStopped: vaultsEvents.EventVaultManagerStopped, + eventDestroy: vaultsEvents.EventVaultManagerDestroy, + eventDestroyed: vaultsEvents.EventVaultManagerDestroyed, + }, ) class VaultManager { static async createVaultManager({ @@ -145,7 +154,7 @@ class VaultManager { }) { this.logger = logger; this.vaultsPath = vaultsPath; - this.efsPath = path.join(this.vaultsPath, config.defaults.efsBase); + this.efsPath = path.join(this.vaultsPath, config.paths.efsBase); this.db = db; this.acl = acl; this.keyRing = keyRing; diff --git a/src/vaults/errors.ts b/src/vaults/errors.ts index f8db78e31..7e184255c 100644 --- a/src/vaults/errors.ts +++ b/src/vaults/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorVaults extends ErrorPolykey {} diff --git a/src/vaults/events.ts b/src/vaults/events.ts new file mode 100644 index 000000000..d7ee9a817 --- /dev/null +++ b/src/vaults/events.ts @@ -0,0 +1,49 @@ +import EventPolykey from '../EventPolykey'; + +abstract class EventVaults extends EventPolykey {} + +abstract class EventVaultInternal extends EventVaults {} + +class EventVaultInternalStart extends EventVaultInternal {} + +class EventVaultInternalStarted extends EventVaultInternal {} + +class EventVaultInternalStop extends EventVaultInternal {} + +class EventVaultInternalStopped extends EventVaultInternal {} + +class EventVaultInternalDestroy extends EventVaultInternal {} + +class EventVaultInternalDestroyed extends EventVaultInternal {} + +abstract class EventVaultManager extends EventVaults {} + +class EventVaultManagerStart extends EventVaultManager {} + +class EventVaultManagerStarted extends EventVaultManager {} + +class EventVaultManagerStop extends EventVaultManager {} + +class EventVaultManagerStopped extends EventVaultManager {} + +class EventVaultManagerDestroy extends EventVaultManager {} + +class EventVaultManagerDestroyed extends EventVaultManager {} + +export { + EventVaults, + EventVaultInternal, + EventVaultInternalStart, + EventVaultInternalStarted, + EventVaultInternalStop, + EventVaultInternalStopped, + EventVaultInternalDestroy, + EventVaultInternalDestroyed, + EventVaultManager, + EventVaultManagerStart, + EventVaultManagerStarted, + EventVaultManagerStop, + EventVaultManagerStopped, + EventVaultManagerDestroy, + EventVaultManagerDestroyed, +}; diff --git a/src/vaults/index.ts b/src/vaults/index.ts index 84fc46769..26906d291 100644 --- a/src/vaults/index.ts +++ b/src/vaults/index.ts @@ -4,4 +4,5 @@ export type { Vault } from './Vault'; export * as utils from './utils'; export * as types from './types'; export * as errors from './errors'; +export * as events from './events'; export * as vaultOps from './VaultOps'; diff --git a/src/websockets/errors.ts b/src/websockets/errors.ts index 0617d08a4..fe94fbaf8 100644 --- a/src/websockets/errors.ts +++ b/src/websockets/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorWebSocket extends ErrorPolykey {} diff --git a/src/workers/errors.ts b/src/workers/errors.ts index 17182fa0c..880e94356 100644 --- a/src/workers/errors.ts +++ b/src/workers/errors.ts @@ -1,4 +1,5 @@ -import { ErrorPolykey, sysexits } from '../errors'; +import ErrorPolykey from '../ErrorPolykey'; +import sysexits from '../utils/sysexits'; class ErrorWorkers extends ErrorPolykey {} diff --git a/tests/PolykeyAgent.test.ts b/tests/PolykeyAgent.test.ts index 9279b3159..d657e3c42 100644 --- a/tests/PolykeyAgent.test.ts +++ b/tests/PolykeyAgent.test.ts @@ -14,6 +14,7 @@ import * as keysUtils from '@/keys/utils/index'; describe('PolykeyAgent', () => { const password = 'password'; + const localhost = '127.0.0.1'; const logger = new Logger('PolykeyAgent Test', LogLevel.WARN, [ new StreamHandler(), ]); @@ -33,13 +34,17 @@ describe('PolykeyAgent', () => { const nodePath = path.join(dataDir, 'polykey'); const pkAgent = await PolykeyAgent.createPolykeyAgent({ password, - nodePath, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath, + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, + logger, }); await expect(pkAgent.destroy(password)).rejects.toThrow( errors.ErrorPolykeyAgentRunning, @@ -56,53 +61,61 @@ describe('PolykeyAgent', () => { const nodePath = `${dataDir}/polykey`; const pkAgent = await PolykeyAgent.createPolykeyAgent({ password, - nodePath, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath, + workers: 0, + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, - workers: 0, + logger, }); let nodePathContents = await fs.promises.readdir(nodePath); - expect(nodePathContents).toContain(config.defaults.statusBase); - expect(nodePathContents).toContain(config.defaults.stateBase); + expect(nodePathContents).toContain(config.paths.statusBase); + expect(nodePathContents).toContain(config.paths.stateBase); let stateContents = await fs.promises.readdir( - path.join(nodePath, config.defaults.stateBase), + path.join(nodePath, config.paths.stateBase), ); - expect(stateContents).toContain(config.defaults.keysBase); - expect(stateContents).toContain(config.defaults.dbBase); - expect(stateContents).toContain(config.defaults.vaultsBase); + expect(stateContents).toContain(config.paths.keysBase); + expect(stateContents).toContain(config.paths.dbBase); + expect(stateContents).toContain(config.paths.vaultsBase); await pkAgent.stop(); nodePathContents = await fs.promises.readdir(nodePath); - expect(nodePathContents).toContain(config.defaults.statusBase); - expect(nodePathContents).toContain(config.defaults.stateBase); + expect(nodePathContents).toContain(config.paths.statusBase); + expect(nodePathContents).toContain(config.paths.stateBase); stateContents = await fs.promises.readdir( - path.join(nodePath, config.defaults.stateBase), + path.join(nodePath, config.paths.stateBase), ); - expect(stateContents).toContain(config.defaults.keysBase); - expect(stateContents).toContain(config.defaults.dbBase); - expect(stateContents).toContain(config.defaults.vaultsBase); + expect(stateContents).toContain(config.paths.keysBase); + expect(stateContents).toContain(config.paths.dbBase); + expect(stateContents).toContain(config.paths.vaultsBase); await pkAgent.destroy(password); nodePathContents = await fs.promises.readdir(nodePath); // The status will be the only file left over expect(nodePathContents).toHaveLength(1); - expect(nodePathContents).toContain(config.defaults.statusBase); + expect(nodePathContents).toContain(config.paths.statusBase); }); test('start after stop', async () => { const nodePath = `${dataDir}/polykey`; - const statusPath = path.join(nodePath, config.defaults.statusBase); - const statusLockPath = path.join(nodePath, config.defaults.statusLockBase); + const statusPath = path.join(nodePath, config.paths.statusBase); + const statusLockPath = path.join(nodePath, config.paths.statusLockBase); const pkAgent = await PolykeyAgent.createPolykeyAgent({ password, - nodePath, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath, + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, + logger, }); const status = new Status({ statusPath, @@ -125,19 +138,23 @@ describe('PolykeyAgent', () => { }); test('schema state version is maintained after start and stop', async () => { const nodePath = path.join(dataDir, 'polykey'); - const statePath = path.join(nodePath, config.defaults.stateBase); + const statePath = path.join(nodePath, config.paths.stateBase); const schema = new Schema({ statePath, }); const pkAgent = await PolykeyAgent.createPolykeyAgent({ password, - nodePath, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath, + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, + logger, }); expect(await schema.readVersion()).toBe(config.stateVersion); await pkAgent.stop(); @@ -146,7 +163,7 @@ describe('PolykeyAgent', () => { }); test('cannot start during state version mismatch', async () => { const nodePath = path.join(dataDir, 'polykey'); - const statePath = path.join(nodePath, config.defaults.stateBase); + const statePath = path.join(nodePath, config.paths.stateBase); await fs.promises.mkdir(nodePath); let schema = await Schema.createSchema({ statePath, @@ -158,13 +175,17 @@ describe('PolykeyAgent', () => { await expect( PolykeyAgent.createPolykeyAgent({ password, - nodePath, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath, + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, + logger, }), ).rejects.toThrow(errors.ErrorSchemaVersionTooNew); // The 0 version will always be too old @@ -179,13 +200,17 @@ describe('PolykeyAgent', () => { await expect( PolykeyAgent.createPolykeyAgent({ password, - nodePath, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath, + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, + logger, }), ).rejects.toThrow(errors.ErrorSchemaVersionTooOld); }); @@ -195,13 +220,17 @@ describe('PolykeyAgent', () => { try { pkAgent = await PolykeyAgent.createPolykeyAgent({ password, - nodePath, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath, + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, + logger, }); const prom = promise(); pkAgent.events.on( @@ -224,13 +253,17 @@ describe('PolykeyAgent', () => { try { pkAgent = await PolykeyAgent.createPolykeyAgent({ password, - nodePath, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath, + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, + logger, }); const prom = promise(); pkAgent.events.on( @@ -253,13 +286,17 @@ describe('PolykeyAgent', () => { try { pkAgent = await PolykeyAgent.createPolykeyAgent({ password, - nodePath, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath, + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, + logger, }); const prom = promise(); pkAgent.events.on( diff --git a/tests/PolykeyClient.test.ts b/tests/PolykeyClient.test.ts index 63ef7c6b7..0b5f391a4 100644 --- a/tests/PolykeyClient.test.ts +++ b/tests/PolykeyClient.test.ts @@ -11,6 +11,7 @@ import WebSocketClient from '@/websockets/WebSocketClient'; describe('PolykeyClient', () => { const password = 'password'; + const localhost = '127.0.0.1'; const logger = new Logger('PolykeyClient Test', LogLevel.WARN, [ new StreamHandler(), ]); @@ -24,13 +25,17 @@ describe('PolykeyClient', () => { nodePath = path.join(dataDir, 'polykey'); pkAgent = await PolykeyAgent.createPolykeyAgent({ password, - nodePath, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath, + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, + logger, }); }); afterEach(async () => { @@ -42,7 +47,7 @@ describe('PolykeyClient', () => { }); test('preserving and destroying session state', async () => { const session = await Session.createSession({ - sessionTokenPath: path.join(nodePath, config.defaults.tokenBase), + sessionTokenPath: path.join(nodePath, config.paths.tokenBase), fs, logger, }); @@ -60,7 +65,6 @@ describe('PolykeyClient', () => { fs, logger, fresh: true, - rpcClientClient: { destroy: () => {} } as any, }); expect(await session.readToken()).toBeUndefined(); await session.writeToken('abc' as SessionToken); diff --git a/tests/agent/handlers/nodesClaimsGet.test.ts b/tests/agent/handlers/nodesClaimsGet.test.ts index 9ca427a0b..d409b78f1 100644 --- a/tests/agent/handlers/nodesClaimsGet.test.ts +++ b/tests/agent/handlers/nodesClaimsGet.test.ts @@ -1,7 +1,6 @@ import type { IdentityId, ProviderId } from '@/identities/types'; import type { ClaimIdEncoded } from '@/ids'; import type * as quicEvents from '@matrixai/quic/dist/events'; -import type { Host as QUICHost } from '@matrixai/quic'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -26,7 +25,7 @@ describe('nodesClaimsGet', () => { ]); const password = 'password'; const crypto = tlsTestsUtils.createCrypto(); - const localHost = '127.0.0.1' as QUICHost; + const localHost = '127.0.0.1'; let dataDir: string; diff --git a/tests/agent/handlers/nodesClosestLocalNode.test.ts b/tests/agent/handlers/nodesClosestLocalNode.test.ts index 03bdc7305..41910b8a9 100644 --- a/tests/agent/handlers/nodesClosestLocalNode.test.ts +++ b/tests/agent/handlers/nodesClosestLocalNode.test.ts @@ -1,5 +1,4 @@ import type * as quicEvents from '@matrixai/quic/dist/events'; -import type { Host as QUICHost } from '@matrixai/quic'; import type { NodeIdEncoded } from '@/ids'; import type { Host, Port } from '@/network/types'; import fs from 'fs'; @@ -25,7 +24,7 @@ describe('nodesClosestLocalNode', () => { ]); const password = 'password'; const crypto = tlsTestsUtils.createCrypto(); - const localHost = '127.0.0.1' as QUICHost; + const localHost = '127.0.0.1'; let dataDir: string; diff --git a/tests/agent/handlers/nodesCrossSignClaim.test.ts b/tests/agent/handlers/nodesCrossSignClaim.test.ts index a522864c9..faf39d9b2 100644 --- a/tests/agent/handlers/nodesCrossSignClaim.test.ts +++ b/tests/agent/handlers/nodesCrossSignClaim.test.ts @@ -1,5 +1,4 @@ import type * as quicEvents from '@matrixai/quic/dist/events'; -import type { Host as QUICHost } from '@matrixai/quic'; import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; import type { AgentClaimMessage } from '@/agent/handlers/types'; import type { NodeId } from '@/ids'; @@ -35,7 +34,7 @@ describe('nodesCrossSignClaim', () => { ]); const password = 'password'; const crypto = tlsTestsUtils.createCrypto(); - const localHost = '127.0.0.1' as QUICHost; + const localHost = '127.0.0.1'; let dataDir: string; diff --git a/tests/agent/handlers/nodesHolePunchMessage.test.ts b/tests/agent/handlers/nodesHolePunchMessage.test.ts index 22d44aa6f..edc675986 100644 --- a/tests/agent/handlers/nodesHolePunchMessage.test.ts +++ b/tests/agent/handlers/nodesHolePunchMessage.test.ts @@ -1,5 +1,4 @@ import type * as quicEvents from '@matrixai/quic/dist/events'; -import type { Host as QUICHost } from '@matrixai/quic'; import type GestaltGraph from '../../../src/gestalts/GestaltGraph'; import fs from 'fs'; import path from 'path'; @@ -28,7 +27,7 @@ describe('nodesHolePunchMessage', () => { ]); const password = 'password'; const crypto = tlsTestsUtils.createCrypto(); - const localHost = '127.0.0.1' as QUICHost; + const localHost = '127.0.0.1'; let dataDir: string; diff --git a/tests/agent/handlers/notificationsSend.test.ts b/tests/agent/handlers/notificationsSend.test.ts index 3409628d5..c32b7cc52 100644 --- a/tests/agent/handlers/notificationsSend.test.ts +++ b/tests/agent/handlers/notificationsSend.test.ts @@ -1,13 +1,13 @@ import type * as quicEvents from '@matrixai/quic/dist/events'; -import type { Host as QUICHost } from '@matrixai/quic'; import type { Notification, SignedNotification } from '@/notifications/types'; import type { NodeId } from '@/ids'; import type GestaltGraph from '../../../src/gestalts/GestaltGraph'; +import type { Host } from '@/network/types'; import fs from 'fs'; import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { QUICClient, QUICServer, QUICSocket } from '@matrixai/quic'; +import { QUICClient, QUICServer } from '@matrixai/quic'; import { DB } from '@matrixai/db'; import RPCClient from '@/rpc/RPCClient'; import RPCServer from '@/rpc/RPCServer'; @@ -36,7 +36,7 @@ describe('notificationsSend', () => { ]); const password = 'password'; const crypto = tlsTestsUtils.createCrypto(); - const localHost = '127.0.0.1' as QUICHost; + const localHost = '127.0.0.1' as Host; let dataDir: string; @@ -46,7 +46,6 @@ describe('notificationsSend', () => { let acl: ACL; let sigchain: Sigchain; let taskManager: TaskManager; - let quicSocket: QUICSocket; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; let notificationsManager: NotificationsManager; @@ -118,23 +117,15 @@ describe('notificationsSend', () => { logger, lazy: true, }); - quicSocket = new QUICSocket({ - logger, - }); - await quicSocket.start({ - host: localHost, - }); const tlsConfigClient = await tlsTestsUtils.createTLSConfig( keyRing.keyPair, ); nodeConnectionManager = new NodeConnectionManager({ tlsConfig: tlsConfigClient, - crypto, - quicSocket, keyRing, nodeGraph, - connectionConnectTime: 2000, - connectionTimeoutTime: 2000, + connectionConnectTimeoutTime: 2000, + connectionIdleTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); nodeManager = new NodeManager({ @@ -148,7 +139,7 @@ describe('notificationsSend', () => { logger, }); await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager, handleStream: () => {} }); + await nodeConnectionManager.start({ host: localHost }); await taskManager.startProcessing(); notificationsManager = await NotificationsManager.createNotificationsManager({ @@ -253,7 +244,6 @@ describe('notificationsSend', () => { await notificationsManager.stop(); await nodeManager.stop(); await nodeConnectionManager.stop(); - await quicSocket.stop(); await sigchain.stop(); await acl.stop(); await nodeGraph.stop(); diff --git a/tests/bootstrap/utils.test.ts b/tests/bootstrap/utils.test.ts index b1936c6c2..615eb9351 100644 --- a/tests/bootstrap/utils.test.ts +++ b/tests/bootstrap/utils.test.ts @@ -28,7 +28,9 @@ describe('bootstrap/utils', () => { const password = 'password'; const recoveryCode = await bootstrapUtils.bootstrapState({ password, - nodePath, + options: { + nodePath, + }, fs, logger, }); @@ -39,14 +41,14 @@ describe('bootstrap/utils', () => { ).toBe(true); const nodePathContents = await fs.promises.readdir(nodePath); expect(nodePathContents.length > 0).toBe(true); - expect(nodePathContents).toContain(config.defaults.statusBase); - expect(nodePathContents).toContain(config.defaults.stateBase); + expect(nodePathContents).toContain(config.paths.statusBase); + expect(nodePathContents).toContain(config.paths.stateBase); const stateContents = await fs.promises.readdir( - path.join(nodePath, config.defaults.stateBase), + path.join(nodePath, config.paths.stateBase), ); - expect(stateContents).toContain(config.defaults.keysBase); - expect(stateContents).toContain(config.defaults.dbBase); - expect(stateContents).toContain(config.defaults.vaultsBase); + expect(stateContents).toContain(config.paths.keysBase); + expect(stateContents).toContain(config.paths.dbBase); + expect(stateContents).toContain(config.paths.vaultsBase); }); test('bootstraps existing but empty node path', async () => { const nodePath = path.join(dataDir, 'polykey'); @@ -54,7 +56,9 @@ describe('bootstrap/utils', () => { const password = 'password'; const recoveryCode = await bootstrapUtils.bootstrapState({ password, - nodePath, + options: { + nodePath, + }, fs, logger, }); @@ -65,14 +69,14 @@ describe('bootstrap/utils', () => { ).toBe(true); const nodePathContents = await fs.promises.readdir(nodePath); expect(nodePathContents.length > 0).toBe(true); - expect(nodePathContents).toContain(config.defaults.statusBase); - expect(nodePathContents).toContain(config.defaults.stateBase); + expect(nodePathContents).toContain(config.paths.statusBase); + expect(nodePathContents).toContain(config.paths.stateBase); const stateContents = await fs.promises.readdir( - path.join(nodePath, config.defaults.stateBase), + path.join(nodePath, config.paths.stateBase), ); - expect(stateContents).toContain(config.defaults.keysBase); - expect(stateContents).toContain(config.defaults.dbBase); - expect(stateContents).toContain(config.defaults.vaultsBase); + expect(stateContents).toContain(config.paths.keysBase); + expect(stateContents).toContain(config.paths.dbBase); + expect(stateContents).toContain(config.paths.vaultsBase); }); test('bootstrap fails if non-empty node path', async () => { // Normal file @@ -87,7 +91,9 @@ describe('bootstrap/utils', () => { await expect( bootstrapUtils.bootstrapState({ password, - nodePath: nodePath1, + options: { + nodePath: nodePath1, + }, fs, logger, }), @@ -103,7 +109,9 @@ describe('bootstrap/utils', () => { await expect( bootstrapUtils.bootstrapState({ password, - nodePath: nodePath2, + options: { + nodePath: nodePath2, + }, fs, logger, }), @@ -115,7 +123,9 @@ describe('bootstrap/utils', () => { await expect( bootstrapUtils.bootstrapState({ password, - nodePath: nodePath3, + options: { + nodePath: nodePath3, + }, fs, logger, }), @@ -127,13 +137,17 @@ describe('bootstrap/utils', () => { const [result1, result2] = await Promise.allSettled([ bootstrapUtils.bootstrapState({ password, - nodePath, + options: { + nodePath, + }, fs, logger, }), bootstrapUtils.bootstrapState({ password, - nodePath, + options: { + nodePath, + }, fs, logger, }), diff --git a/tests/client/handlers/agent.test.ts b/tests/client/handlers/agent.test.ts index a7192e624..00cbb058d 100644 --- a/tests/client/handlers/agent.test.ts +++ b/tests/client/handlers/agent.test.ts @@ -142,14 +142,16 @@ describe('agentStatus', () => { ); const nodePath = path.join(dataDir, 'node'); pkAgent = await PolykeyAgent.createPolykeyAgent({ - nodePath, password, - logger, - keyRingConfig: { - strictMemoryLock: false, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, + options: { + nodePath, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, + logger, }); tlsConfig = await testsUtils.createTLSConfig(pkAgent.keyRing.keyPair); }); @@ -200,8 +202,8 @@ describe('agentStatus', () => { nodeIdEncoded: nodesUtils.encodeNodeId(pkAgent.keyRing.getNodeId()), clientHost: pkAgent.webSocketServerClient.getHost(), clientPort: pkAgent.webSocketServerClient.getPort(), - agentHost: pkAgent.quicSocket.host, - agentPort: pkAgent.quicSocket.port, + agentHost: pkAgent.nodeConnectionManager.host, + agentPort: pkAgent.nodeConnectionManager.port, publicKeyJwk: keysUtils.publicKeyToJWK(pkAgent.keyRing.keyPair.publicKey), certChainPEM: await pkAgent.certManager.getCertPEMsChainPEM(), }); @@ -248,13 +250,15 @@ describe('agentStop', () => { tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); pkAgent = await PolykeyAgent.createPolykeyAgent({ password, - nodePath, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, + logger, }); }); afterEach(async () => { @@ -299,8 +303,8 @@ describe('agentStop', () => { }); // Doing the test - const statusPath = path.join(nodePath, config.defaults.statusBase); - const statusLockPath = path.join(nodePath, config.defaults.statusLockBase); + const statusPath = path.join(nodePath, config.paths.statusBase); + const statusLockPath = path.join(nodePath, config.paths.statusLockBase); const status = new Status({ statusPath, statusLockPath, diff --git a/tests/client/handlers/gestalts.test.ts b/tests/client/handlers/gestalts.test.ts index fa3b21e2f..470cd0221 100644 --- a/tests/client/handlers/gestalts.test.ts +++ b/tests/client/handlers/gestalts.test.ts @@ -18,7 +18,6 @@ import path from 'path'; import os from 'os'; import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; -import { QUICSocket } from '@matrixai/quic'; import KeyRing from '@/keys/KeyRing'; import * as keysUtils from '@/keys/utils'; import RPCServer from '@/rpc/RPCServer'; @@ -82,7 +81,7 @@ describe('gestaltsActionsByIdentity', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -158,13 +157,13 @@ describe('gestaltsActionsByIdentity', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -219,9 +218,8 @@ describe('gestaltsActionsByIdentity', () => { }; await rpcClient.methods.gestaltsActionsSetByIdentity(setActionMessage); // Check for permission - const getSetResponse = await rpcClient.methods.gestaltsActionsGetByIdentity( - providerMessage, - ); + const getSetResponse = + await rpcClient.methods.gestaltsActionsGetByIdentity(providerMessage); expect(getSetResponse.actionsList).toContainEqual(action); // Unset permission await rpcClient.methods.gestaltsActionsUnsetByIdentity(setActionMessage); @@ -238,7 +236,7 @@ describe('gestaltsActionsByNode', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -313,13 +311,13 @@ describe('gestaltsActionsByNode', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -350,16 +348,14 @@ describe('gestaltsActionsByNode', () => { }; await rpcClient.methods.gestaltsActionsSetByNode(requestMessage); // Check for permission - const getSetResponse = await rpcClient.methods.gestaltsActionsGetByNode( - nodeMessage, - ); + const getSetResponse = + await rpcClient.methods.gestaltsActionsGetByNode(nodeMessage); expect(getSetResponse.actionsList).toContainEqual(action); // Unset permission await rpcClient.methods.gestaltsActionsUnsetByNode(requestMessage); // Check permission was removed - const getUnsetResponse = await rpcClient.methods.gestaltsActionsGetByNode( - nodeMessage, - ); + const getUnsetResponse = + await rpcClient.methods.gestaltsActionsGetByNode(nodeMessage); expect(getUnsetResponse.actionsList).toHaveLength(0); }); }); @@ -370,7 +366,7 @@ describe('gestaltsDiscoverByIdentity', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -384,7 +380,6 @@ describe('gestaltsDiscoverByIdentity', () => { let nodeGraph: NodeGraph; let sigchain: Sigchain; let nodeManager: NodeManager; - let quicSocket: QUICSocket; let nodeConnectionManager: NodeConnectionManager; let discovery: Discovery; @@ -438,22 +433,13 @@ describe('gestaltsDiscoverByIdentity', () => { keyRing, logger: logger.getChild('NodeGraph'), }); - const crypto = tlsTestsUtils.createCrypto(); - quicSocket = new QUICSocket({ - logger, - }); - await quicSocket.start({ - host: '127.0.0.1', - }); nodeConnectionManager = new NodeConnectionManager({ keyRing, nodeGraph, // @ts-ignore: TLS not needed for this test tlsConfig: {}, - crypto, - quicSocket, - connectionConnectTime: 2000, - connectionTimeoutTime: 2000, + connectionConnectTimeoutTime: 2000, + connectionIdleTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); nodeManager = new NodeManager({ @@ -467,7 +453,9 @@ describe('gestaltsDiscoverByIdentity', () => { logger, }); await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager, handleStream: () => {} }); + await nodeConnectionManager.start({ + host: localhost as Host, + }); discovery = await Discovery.createDiscovery({ db, gestaltGraph, @@ -485,7 +473,6 @@ describe('gestaltsDiscoverByIdentity', () => { await discovery.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); - await quicSocket.stop(); await nodeManager.stop(); await sigchain.stop(); await identitiesManager.stop(); @@ -513,13 +500,13 @@ describe('gestaltsDiscoverByIdentity', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -555,7 +542,7 @@ describe('gestaltsDiscoverByNode', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -569,7 +556,6 @@ describe('gestaltsDiscoverByNode', () => { let nodeGraph: NodeGraph; let sigchain: Sigchain; let nodeManager: NodeManager; - let quicSocket: QUICSocket; let nodeConnectionManager: NodeConnectionManager; let discovery: Discovery; @@ -623,22 +609,13 @@ describe('gestaltsDiscoverByNode', () => { keyRing, logger: logger.getChild('NodeGraph'), }); - const crypto = tlsTestsUtils.createCrypto(); - quicSocket = new QUICSocket({ - logger, - }); - await quicSocket.start({ - host: '127.0.0.1', - }); nodeConnectionManager = new NodeConnectionManager({ keyRing, nodeGraph, // @ts-ignore: TLS not needed for this test tlsConfig: {}, - crypto, - quicSocket, - connectionConnectTime: 2000, - connectionTimeoutTime: 2000, + connectionConnectTimeoutTime: 2000, + connectionIdleTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); nodeManager = new NodeManager({ @@ -652,7 +629,7 @@ describe('gestaltsDiscoverByNode', () => { logger, }); await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager, handleStream: () => {} }); + await nodeConnectionManager.start({ host: localhost as Host }); discovery = await Discovery.createDiscovery({ db, gestaltGraph, @@ -670,7 +647,6 @@ describe('gestaltsDiscoverByNode', () => { await discovery.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); - await quicSocket.stop(); await nodeManager.stop(); await sigchain.stop(); await identitiesManager.stop(); @@ -698,13 +674,13 @@ describe('gestaltsDiscoverByNode', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -741,7 +717,7 @@ describe('gestaltsGestaltGetByIdentity', () => { ], ); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -808,13 +784,13 @@ describe('gestaltsGestaltGetByIdentity', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -876,9 +852,8 @@ describe('gestaltsGestaltGetByIdentity', () => { providerId: identity.providerId, identityId: identity.identityId, }; - const response = await rpcClient.methods.gestaltsGestaltGetByIdentity( - request, - ); + const response = + await rpcClient.methods.gestaltsGestaltGetByIdentity(request); expect(response.gestalt).toEqual(expectedGestalt); }); }); @@ -889,7 +864,7 @@ describe('gestaltsGestaltGetByNode', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -956,13 +931,13 @@ describe('gestaltsGestaltGetByNode', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -1034,7 +1009,7 @@ describe('gestaltsGestaltList', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -1101,13 +1076,13 @@ describe('gestaltsGestaltList', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -1174,7 +1149,7 @@ describe('gestaltsGestaltTrustByIdentity', () => { ], ); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -1188,7 +1163,6 @@ describe('gestaltsGestaltTrustByIdentity', () => { let nodeGraph: NodeGraph; let sigchain: Sigchain; let nodeManager: NodeManager; - let quicSocket: QUICSocket; let nodeConnectionManager: NodeConnectionManager; let discovery: Discovery; let testProvider: TestProvider; @@ -1246,22 +1220,13 @@ describe('gestaltsGestaltTrustByIdentity', () => { keyRing, logger: logger.getChild('NodeGraph'), }); - const crypto = tlsTestsUtils.createCrypto(); - quicSocket = new QUICSocket({ - logger, - }); - await quicSocket.start({ - host: '127.0.0.1', - }); nodeConnectionManager = new NodeConnectionManager({ keyRing, nodeGraph, // @ts-ignore: TLS not needed for this test tlsConfig: {}, - crypto, - quicSocket, - connectionConnectTime: 2000, - connectionTimeoutTime: 2000, + connectionConnectTimeoutTime: 2000, + connectionIdleTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); nodeManager = new NodeManager({ @@ -1275,7 +1240,7 @@ describe('gestaltsGestaltTrustByIdentity', () => { logger, }); await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager, handleStream: () => {} }); + await nodeConnectionManager.start({ host: localhost as Host }); discovery = await Discovery.createDiscovery({ db, gestaltGraph, @@ -1312,7 +1277,6 @@ describe('gestaltsGestaltTrustByIdentity', () => { await discovery.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); - await quicSocket.stop(); await nodeManager.stop(); await sigchain.stop(); await identitiesManager.stop(); @@ -1343,13 +1307,13 @@ describe('gestaltsGestaltTrustByIdentity', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -1405,13 +1369,13 @@ describe('gestaltsGestaltTrustByIdentity', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -1464,13 +1428,13 @@ describe('gestaltsGestaltTrustByIdentity', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -1515,13 +1479,13 @@ describe('gestaltsGestaltTrustByIdentity', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -1581,13 +1545,13 @@ describe('gestaltsGestaltTrustByIdentity', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -1644,7 +1608,7 @@ describe('gestaltsGestaltTrustByNode', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -1658,7 +1622,6 @@ describe('gestaltsGestaltTrustByNode', () => { let nodeGraph: NodeGraph; let sigchain: Sigchain; let nodeManager: NodeManager; - let quicSocket: QUICSocket; let nodeConnectionManager: NodeConnectionManager; let discovery: Discovery; let testProvider: TestProvider; @@ -1684,17 +1647,17 @@ describe('gestaltsGestaltTrustByNode', () => { const nodePath = path.join(nodeDataDir, 'polykey'); node = await PolykeyAgent.createPolykeyAgent({ password, - nodePath, - networkConfig: { - agentHost: '127.0.0.1', - clientHost: '127.0.0.1', + options: { + nodePath, + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }, }); nodeIdRemote = node.keyRing.getNodeId(); nodeIdEncodedRemote = nodesUtils.encodeNodeId(nodeIdRemote); @@ -1765,22 +1728,13 @@ describe('gestaltsGestaltTrustByNode', () => { keyRing, logger: logger.getChild('NodeGraph'), }); - const crypto = tlsTestsUtils.createCrypto(); - quicSocket = new QUICSocket({ - logger, - }); - await quicSocket.start({ - host: '127.0.0.1', - }); nodeConnectionManager = new NodeConnectionManager({ keyRing, nodeGraph, // @ts-ignore: TLS not needed for this test tlsConfig: {}, - crypto, - quicSocket, - connectionConnectTime: 2000, - connectionTimeoutTime: 2000, + connectionConnectTimeoutTime: 2000, + connectionIdleTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); nodeManager = new NodeManager({ @@ -1794,10 +1748,10 @@ describe('gestaltsGestaltTrustByNode', () => { logger, }); await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager, handleStream: () => {} }); + await nodeConnectionManager.start({ host: localhost as Host }); await nodeManager.setNode(nodeIdRemote, { - host: node.quicSocket.host as Host, - port: node.quicSocket.port as Port, + host: node.nodeConnectionManager.host as Host, + port: node.nodeConnectionManager.port as Port, }); discovery = await Discovery.createDiscovery({ db, @@ -1816,7 +1770,6 @@ describe('gestaltsGestaltTrustByNode', () => { await discovery.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); - await quicSocket.stop(); await nodeManager.stop(); await sigchain.stop(); await identitiesManager.stop(); @@ -1852,13 +1805,13 @@ describe('gestaltsGestaltTrustByNode', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -1896,13 +1849,13 @@ describe('gestaltsGestaltTrustByNode', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -1939,13 +1892,13 @@ describe('gestaltsGestaltTrustByNode', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); diff --git a/tests/client/handlers/identities.test.ts b/tests/client/handlers/identities.test.ts index 210b70149..d4d3e1a22 100644 --- a/tests/client/handlers/identities.test.ts +++ b/tests/client/handlers/identities.test.ts @@ -2752,16 +2752,14 @@ describe('identitiesTokenPutDeleteGet', () => { token: testToken.providerToken, }); // Get token - const getPutResponse = await rpcClient.methods.identitiesTokenGet( - providerMessage, - ); + const getPutResponse = + await rpcClient.methods.identitiesTokenGet(providerMessage); expect(getPutResponse.token).toStrictEqual(testToken.providerToken); // Delete token await rpcClient.methods.identitiesTokenDelete(providerMessage); // Check token was deleted - const getDeleteResponse = await rpcClient.methods.identitiesTokenGet( - providerMessage, - ); + const getDeleteResponse = + await rpcClient.methods.identitiesTokenGet(providerMessage); expect(getDeleteResponse.token).toBeUndefined(); }); }); diff --git a/tests/client/handlers/keys.test.ts b/tests/client/handlers/keys.test.ts index b76c84647..d8eab0111 100644 --- a/tests/client/handlers/keys.test.ts +++ b/tests/client/handlers/keys.test.ts @@ -52,7 +52,7 @@ describe('keysCertsChainGet', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; const certs = ['cert1', 'cert2', 'cert3'] as Array; let dataDir: string; let db: DB; @@ -131,13 +131,13 @@ describe('keysCertsChainGet', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -165,7 +165,7 @@ describe('keysCertsGet', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -243,13 +243,13 @@ describe('keysCertsGet', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -273,7 +273,7 @@ describe('keysEncryptDecrypt', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -335,13 +335,13 @@ describe('keysEncryptDecrypt', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -372,7 +372,7 @@ describe('keysKeyPair', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -431,13 +431,13 @@ describe('keysKeyPair', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -476,7 +476,7 @@ describe('keysKeyPairRenew', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let pkAgent: PolykeyAgent; let webSocketClient: WebSocketClient; @@ -492,13 +492,17 @@ describe('keysKeyPairRenew', () => { const nodePath = path.join(dataDir, 'polykey'); pkAgent = await PolykeyAgent.createPolykeyAgent({ password, - nodePath, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath, + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, + logger, }); tlsConfig = await testsUtils.createTLSConfig(pkAgent.keyRing.keyPair); }); @@ -524,13 +528,13 @@ describe('keysKeyPairRenew', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [pkAgent.keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -592,7 +596,7 @@ describe('keysKeyPairReset', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let webSocketClient: WebSocketClient; let webSocketServer: WebSocketServer; @@ -608,13 +612,17 @@ describe('keysKeyPairReset', () => { const nodePath = path.join(dataDir, 'polykey'); pkAgent = await PolykeyAgent.createPolykeyAgent({ password, - nodePath, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath, + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, + logger, }); tlsConfig = await testsUtils.createTLSConfig(pkAgent.keyRing.keyPair); }); @@ -640,13 +648,13 @@ describe('keysKeyPairReset', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [pkAgent.keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -708,7 +716,7 @@ describe('keysPasswordChange', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -767,13 +775,13 @@ describe('keysPasswordChange', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -802,7 +810,7 @@ describe('keysPublicKey', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -861,13 +869,13 @@ describe('keysPublicKey', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); @@ -898,7 +906,7 @@ describe('keysSignVerify', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const localhost = '127.0.0.1'; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -960,13 +968,13 @@ describe('keysSignVerify', () => { }); webSocketServer = await WebSocketServer.createWebSocketServer({ connectionCallback: (streamPair) => rpcServer.handleStream(streamPair), - host, + host: localhost, tlsConfig, logger: logger.getChild('server'), }); webSocketClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], - host, + host: localhost, logger: logger.getChild('client'), port: webSocketServer.getPort(), }); diff --git a/tests/client/handlers/nodes.test.ts b/tests/client/handlers/nodes.test.ts index 1d7477a07..df2142b2e 100644 --- a/tests/client/handlers/nodes.test.ts +++ b/tests/client/handlers/nodes.test.ts @@ -1,4 +1,3 @@ -import type { Host as QUICHost } from '@matrixai/quic'; import type { TLSConfig } from '@/network/types'; import type GestaltGraph from '@/gestalts/GestaltGraph'; import type { NodeIdEncoded } from '@/ids/types'; @@ -9,7 +8,6 @@ import path from 'path'; import os from 'os'; import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; -import { QUICSocket } from '@matrixai/quic'; import KeyRing from '@/keys/KeyRing'; import * as keysUtils from '@/keys/utils'; import RPCServer from '@/rpc/RPCServer'; @@ -45,7 +43,7 @@ describe('nodesAdd', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const host = '127.0.0.1' as Host; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -55,7 +53,6 @@ describe('nodesAdd', () => { let nodeGraph: NodeGraph; let taskManager: TaskManager; let nodeConnectionManager: NodeConnectionManager; - let quicSocket: QUICSocket; let nodeManager: NodeManager; let sigchain: Sigchain; @@ -93,22 +90,13 @@ describe('nodesAdd', () => { logger, lazy: true, }); - const crypto = tlsTestsUtils.createCrypto(); - quicSocket = new QUICSocket({ - logger, - }); - await quicSocket.start({ - host: '127.0.0.1' as QUICHost, - }); nodeConnectionManager = new NodeConnectionManager({ keyRing, nodeGraph, - // @ts-ignore: TLS not needed for this test - tlsConfig: {}, - crypto, - quicSocket, - connectionConnectTime: 2000, - connectionTimeoutTime: 2000, + // TLS not needed for this test + tlsConfig: {} as TLSConfig, + connectionConnectTimeoutTime: 2000, + connectionIdleTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); nodeManager = new NodeManager({ @@ -122,7 +110,7 @@ describe('nodesAdd', () => { logger, }); await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager, handleStream: () => {} }); + await nodeConnectionManager.start({ host }); await taskManager.startProcessing(); }); afterEach(async () => { @@ -130,7 +118,6 @@ describe('nodesAdd', () => { await taskManager.stopTasks(); await nodeGraph.stop(); await nodeConnectionManager.stop(); - await quicSocket.stop(); await nodeManager.stop(); await sigchain.stop(); await db.stop(); @@ -269,7 +256,7 @@ describe('nodesClaim', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const host = '127.0.0.1' as Host; const dummyNotification: Notification = { typ: 'notification', data: { @@ -288,7 +275,6 @@ describe('nodesClaim', () => { let nodeGraph: NodeGraph; let taskManager: TaskManager; let nodeConnectionManager: NodeConnectionManager; - let quicSocket: QUICSocket; let nodeManager: NodeManager; let notificationsManager: NotificationsManager; let acl: ACL; @@ -346,22 +332,13 @@ describe('nodesClaim', () => { logger, lazy: true, }); - const crypto = tlsTestsUtils.createCrypto(); - quicSocket = new QUICSocket({ - logger, - }); - await quicSocket.start({ - host: '127.0.0.1' as QUICHost, - }); nodeConnectionManager = new NodeConnectionManager({ keyRing, nodeGraph, - // @ts-ignore: TLS not needed for this test - tlsConfig: {}, - crypto, - quicSocket, - connectionConnectTime: 2000, - connectionTimeoutTime: 2000, + // TLS not needed for this test + tlsConfig: {} as TLSConfig, + connectionConnectTimeoutTime: 2000, + connectionIdleTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); nodeManager = new NodeManager({ @@ -375,7 +352,7 @@ describe('nodesClaim', () => { logger, }); await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager, handleStream: () => {} }); + await nodeConnectionManager.start({ host }); await taskManager.startProcessing(); notificationsManager = await NotificationsManager.createNotificationsManager({ @@ -396,7 +373,6 @@ describe('nodesClaim', () => { await webSocketServer.stop(true); await webSocketClient.destroy(true); await nodeConnectionManager.stop(); - await quicSocket.stop(); await nodeManager.stop(); await nodeGraph.stop(); await notificationsManager.stop(); @@ -496,7 +472,7 @@ describe('nodesFind', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const host = '127.0.0.1' as Host; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -505,7 +481,6 @@ describe('nodesFind', () => { let tlsConfig: TLSConfig; let nodeGraph: NodeGraph; let taskManager: TaskManager; - let quicSocket: QUICSocket; let nodeConnectionManager: NodeConnectionManager; let sigchain: Sigchain; let mockedFindNode: jest.SpyInstance; @@ -550,28 +525,16 @@ describe('nodesFind', () => { logger, lazy: true, }); - const crypto = tlsTestsUtils.createCrypto(); - quicSocket = new QUICSocket({ - logger, - }); - await quicSocket.start({ - host: '127.0.0.1' as QUICHost, - }); nodeConnectionManager = new NodeConnectionManager({ keyRing, nodeGraph, - // @ts-ignore: TLS not needed for this test - tlsConfig: {}, - crypto, - quicSocket, - connectionConnectTime: 2000, - connectionTimeoutTime: 2000, + // TLS not needed for this test + tlsConfig: {} as TLSConfig, + connectionConnectTimeoutTime: 2000, + connectionIdleTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); - await nodeConnectionManager.start({ - nodeManager: {} as NodeManager, - handleStream: () => {}, - }); + await nodeConnectionManager.start({ host }); await taskManager.startProcessing(); }); afterEach(async () => { @@ -583,7 +546,6 @@ describe('nodesFind', () => { await sigchain.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); - await quicSocket.stop(); await db.stop(); await keyRing.stop(); await taskManager.stop(); @@ -676,7 +638,7 @@ describe('nodesPing', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const host = '127.0.0.1' as Host; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -686,7 +648,6 @@ describe('nodesPing', () => { let nodeGraph: NodeGraph; let taskManager: TaskManager; let nodeConnectionManager: NodeConnectionManager; - let quicSocket: QUICSocket; let nodeManager: NodeManager; let sigchain: Sigchain; let mockedPingNode: jest.SpyInstance; @@ -726,22 +687,13 @@ describe('nodesPing', () => { logger, lazy: true, }); - const crypto = tlsTestsUtils.createCrypto(); - quicSocket = new QUICSocket({ - logger, - }); - await quicSocket.start({ - host: '127.0.0.1' as QUICHost, - }); nodeConnectionManager = new NodeConnectionManager({ keyRing, nodeGraph, - // @ts-ignore: TLS not needed for this test - tlsConfig: {}, - crypto, - quicSocket, - connectionConnectTime: 2000, - connectionTimeoutTime: 2000, + // TLS not needed for this test + tlsConfig: {} as TLSConfig, + connectionConnectTimeoutTime: 2000, + connectionIdleTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); nodeManager = new NodeManager({ @@ -754,7 +706,7 @@ describe('nodesPing', () => { gestaltGraph: {} as GestaltGraph, logger, }); - await nodeConnectionManager.start({ nodeManager, handleStream: () => {} }); + await nodeConnectionManager.start({ host }); await taskManager.startProcessing(); }); afterEach(async () => { @@ -766,7 +718,6 @@ describe('nodesPing', () => { await sigchain.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); - await quicSocket.stop(); await db.stop(); await keyRing.stop(); await taskManager.stop(); diff --git a/tests/client/handlers/notifications.test.ts b/tests/client/handlers/notifications.test.ts index da07a20ac..069bb110c 100644 --- a/tests/client/handlers/notifications.test.ts +++ b/tests/client/handlers/notifications.test.ts @@ -1,5 +1,4 @@ -import type { Host as QUICHost } from '@matrixai/quic'; -import type { TLSConfig } from '@/network/types'; +import type { Host, TLSConfig } from '@/network/types'; import type GestaltGraph from '@/gestalts/GestaltGraph'; import type { General, Notification, VaultShare } from '@/notifications/types'; import type { VaultIdEncoded } from '@/ids/types'; @@ -10,7 +9,6 @@ import path from 'path'; import os from 'os'; import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; -import { QUICSocket } from '@matrixai/quic'; import KeyRing from '@/keys/KeyRing'; import * as keysUtils from '@/keys/utils'; import RPCServer from '@/rpc/RPCServer'; @@ -43,7 +41,7 @@ describe('notificationsClear', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const host = '127.0.0.1' as Host; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -54,7 +52,6 @@ describe('notificationsClear', () => { let taskManager: TaskManager; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; - let quicSocket: QUICSocket; let notificationsManager: NotificationsManager; let acl: ACL; let sigchain: Sigchain; @@ -101,22 +98,13 @@ describe('notificationsClear', () => { logger, lazy: true, }); - const crypto = tlsTestsUtils.createCrypto(); - quicSocket = new QUICSocket({ - logger, - }); - await quicSocket.start({ - host: '127.0.0.1' as QUICHost, - }); nodeConnectionManager = new NodeConnectionManager({ keyRing, nodeGraph, - // @ts-ignore: TLS not needed for this test - tlsConfig: {}, - crypto, - quicSocket, - connectionConnectTime: 2000, - connectionTimeoutTime: 2000, + // TLS not needed for this test + tlsConfig: {} as TLSConfig, + connectionConnectTimeoutTime: 2000, + connectionIdleTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); nodeManager = new NodeManager({ @@ -130,7 +118,7 @@ describe('notificationsClear', () => { logger, }); await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager, handleStream: () => {} }); + await nodeConnectionManager.start({ host }); await taskManager.startProcessing(); notificationsManager = await NotificationsManager.createNotificationsManager({ @@ -156,7 +144,6 @@ describe('notificationsClear', () => { await webSocketClient.destroy(true); await taskManager.stop(); await keyRing.stop(); - await quicSocket.stop({ force: true }); await fs.promises.rm(dataDir, { force: true, recursive: true, @@ -205,7 +192,7 @@ describe('notificationsRead', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const host = '127.0.0.1' as Host; const nodeIdSender = testNodesUtils.generateRandomNodeId(); const nodeIdSenderEncoded = nodesUtils.encodeNodeId(nodeIdSender); const nodeIdReceiverEncoded = 'test'; @@ -219,7 +206,6 @@ describe('notificationsRead', () => { let taskManager: TaskManager; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; - let quicSocket: QUICSocket; let notificationsManager: NotificationsManager; let acl: ACL; let sigchain: Sigchain; @@ -267,22 +253,13 @@ describe('notificationsRead', () => { logger, lazy: true, }); - const crypto = tlsTestsUtils.createCrypto(); - quicSocket = new QUICSocket({ - logger, - }); - await quicSocket.start({ - host: '127.0.0.1' as QUICHost, - }); nodeConnectionManager = new NodeConnectionManager({ keyRing, nodeGraph, - // @ts-ignore: TLS not needed for this test - tlsConfig: {}, - crypto, - quicSocket, - connectionConnectTime: 2000, - connectionTimeoutTime: 2000, + // TLS not needed for this test + tlsConfig: {} as TLSConfig, + connectionConnectTimeoutTime: 2000, + connectionIdleTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); nodeManager = new NodeManager({ @@ -296,7 +273,7 @@ describe('notificationsRead', () => { logger, }); await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager, handleStream: () => {} }); + await nodeConnectionManager.start({ host }); await taskManager.start(); notificationsManager = await NotificationsManager.createNotificationsManager({ @@ -318,7 +295,6 @@ describe('notificationsRead', () => { await sigchain.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); - await quicSocket.stop(); await nodeManager.stop(); await acl.stop(); await db.stop(); @@ -756,7 +732,7 @@ describe('notificationsSend', () => { ), ]); const password = 'helloWorld'; - const host = '127.0.0.1'; + const host = '127.0.0.1' as Host; let dataDir: string; let db: DB; let keyRing: KeyRing; @@ -767,7 +743,6 @@ describe('notificationsSend', () => { let taskManager: TaskManager; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; - let quicSocket: QUICSocket; let notificationsManager: NotificationsManager; let acl: ACL; let sigchain: Sigchain; @@ -815,22 +790,12 @@ describe('notificationsSend', () => { logger, lazy: true, }); - const crypto = tlsTestsUtils.createCrypto(); - quicSocket = new QUICSocket({ - logger, - }); - await quicSocket.start({ - host: '127.0.0.1' as QUICHost, - }); nodeConnectionManager = new NodeConnectionManager({ keyRing, nodeGraph, - // @ts-ignore: TLS not needed for this test - tlsConfig: {}, - crypto, - quicSocket, - connectionConnectTime: 2000, - connectionTimeoutTime: 2000, + tlsConfig: {} as TLSConfig, + connectionConnectTimeoutTime: 2000, + connectionIdleTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); nodeManager = new NodeManager({ @@ -844,7 +809,7 @@ describe('notificationsSend', () => { logger, }); await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager, handleStream: () => {} }); + await nodeConnectionManager.start({ host }); await taskManager.start(); notificationsManager = await NotificationsManager.createNotificationsManager({ @@ -866,7 +831,6 @@ describe('notificationsSend', () => { await sigchain.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); - await quicSocket.stop(); await nodeManager.stop(); await acl.stop(); await db.stop(); diff --git a/tests/discovery/Discovery.test.ts b/tests/discovery/Discovery.test.ts index 1d81ed52f..094e40e30 100644 --- a/tests/discovery/Discovery.test.ts +++ b/tests/discovery/Discovery.test.ts @@ -11,7 +11,6 @@ import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { PromiseCancellable } from '@matrixai/async-cancellable'; import { AsyncIterableX as AsyncIterable } from 'ix/asynciterable'; -import { QUICSocket } from '@matrixai/quic'; import TaskManager from '@/tasks/TaskManager'; import PolykeyAgent from '@/PolykeyAgent'; import Discovery from '@/discovery/Discovery'; @@ -32,11 +31,11 @@ import * as testNodesUtils from '../nodes/utils'; import TestProvider from '../identities/TestProvider'; import { encodeProviderIdentityId } from '../../src/ids/index'; import 'ix/add/asynciterable-operators/toarray'; -import * as tlsTestsUtils from '../utils/tls'; import { createTLSConfig } from '../utils/tls'; describe('Discovery', () => { const password = 'password'; + const localhost = '127.0.0.1'; const logger = new Logger(`${Discovery.name} Test`, LogLevel.WARN, [ new StreamHandler(), ]); @@ -57,7 +56,6 @@ describe('Discovery', () => { let taskManager: TaskManager; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; - let quicSocket: QUICSocket; let db: DB; let acl: ACL; let keyRing: KeyRing; @@ -153,22 +151,13 @@ describe('Discovery', () => { logger, lazy: true, }); - const crypto = tlsTestsUtils.createCrypto(); - quicSocket = new QUICSocket({ - logger, - }); - await quicSocket.start({ - host: '127.0.0.1', - }); const tlsConfig = await createTLSConfig(keyRing.keyPair); nodeConnectionManager = new NodeConnectionManager({ keyRing, nodeGraph, tlsConfig, - crypto, - quicSocket, - connectionConnectTime: 2000, - connectionTimeoutTime: 2000, + connectionConnectTimeoutTime: 2000, + connectionIdleTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); nodeManager = new NodeManager({ @@ -182,42 +171,44 @@ describe('Discovery', () => { logger, }); await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager, handleStream: () => {} }); + await nodeConnectionManager.start({ + host: localhost as Host, + }); // Set up other gestalt nodeA = await PolykeyAgent.createPolykeyAgent({ password: password, - nodePath: path.join(dataDir, 'nodeA'), - networkConfig: { - agentHost: '127.0.0.1', - clientHost: '127.0.0.1', + options: { + nodePath: path.join(dataDir, 'nodeA'), + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, logger: logger.getChild('nodeA'), - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }, }); nodeB = await PolykeyAgent.createPolykeyAgent({ password: password, - nodePath: path.join(dataDir, 'nodeB'), - networkConfig: { - agentHost: '127.0.0.1', - clientHost: '127.0.0.1', + options: { + nodePath: path.join(dataDir, 'nodeB'), + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, logger: logger.getChild('nodeB'), - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }, }); nodeIdA = nodeA.keyRing.getNodeId(); nodeIdB = nodeB.keyRing.getNodeId(); await testNodesUtils.nodesConnect(nodeA, nodeB); await nodeGraph.setNode(nodeA.keyRing.getNodeId(), { - host: nodeA.quicSocket.host as Host, - port: nodeA.quicSocket.port as Port, + host: nodeA.nodeConnectionManager.host as Host, + port: nodeA.nodeConnectionManager.port as Port, }); await nodeB.acl.setNodeAction(nodeA.keyRing.getNodeId(), 'claim'); await nodeA.nodeManager.claimNode(nodeB.keyRing.getNodeId()); @@ -238,7 +229,6 @@ describe('Discovery', () => { await nodeA.stop(); await nodeB.stop(); await nodeConnectionManager.stop(); - await quicSocket.stop(); await nodeManager.stop(); await nodeGraph.stop(); await sigchain.stop(); diff --git a/tests/events/EventBus.test.ts b/tests/events/EventBus.test.ts deleted file mode 100644 index 8a24be430..000000000 --- a/tests/events/EventBus.test.ts +++ /dev/null @@ -1,278 +0,0 @@ -import { EventBus, captureRejectionSymbol } from '@/events'; -import { sleep } from '@/utils'; - -describe('EventBus', () => { - const testSyncFunction = jest.fn((echo: string) => { - return echo; - }); - const testAsyncFunction = jest.fn(async (echo: string) => { - await sleep(10); - return echo; - }); - afterEach(async () => { - testSyncFunction.mockRestore(); - testAsyncFunction.mockRestore(); - }); - test('synchronous handlers simple', async () => { - const e = new EventBus({ - captureRejections: true, - }); - e.on('test-event-1', () => { - testSyncFunction('Handler 1'); - }); - e.on('test-event-2', () => { - testSyncFunction('Handler 2'); - }); - e.on('test-event-1', () => { - testSyncFunction('Handler 3'); - }); - const result1 = await e.emitAsync('test-event-1'); - const result2 = await e.emitAsync('test-event-2'); - expect(result1).toBeTruthy(); - expect(result2).toBeTruthy(); - expect(testSyncFunction.mock.calls[0][0]).toBe('Handler 1'); - expect(testSyncFunction.mock.calls[1][0]).toBe('Handler 3'); - expect(testSyncFunction.mock.calls[2][0]).toBe('Handler 2'); - }); - test('synchronous handlers complex', async () => { - const e = new EventBus({ - captureRejections: true, - }); - e.on('test-event-1', () => { - testSyncFunction('Handler 1 Call 1'); - testSyncFunction('Handler 1 Call 2'); - testSyncFunction('Handler 1 Call 3'); - }); - e.on('test-event-2', () => { - testSyncFunction('Handler 2 Call 1'); - testSyncFunction('Handler 2 Call 2'); - testSyncFunction('Handler 2 Call 3'); - }); - e.on('test-event-1', () => { - testSyncFunction('Handler 3 Call 1'); - testSyncFunction('Handler 3 Call 2'); - testSyncFunction('Handler 3 Call 3'); - }); - const result1 = await e.emitAsync('test-event-1'); - const result2 = await e.emitAsync('test-event-2'); - expect(result1).toBeTruthy(); - expect(result2).toBeTruthy(); - expect(testSyncFunction.mock.calls[0][0]).toBe('Handler 1 Call 1'); - expect(testSyncFunction.mock.calls[1][0]).toBe('Handler 1 Call 2'); - expect(testSyncFunction.mock.calls[2][0]).toBe('Handler 1 Call 3'); - expect(testSyncFunction.mock.calls[3][0]).toBe('Handler 3 Call 1'); - expect(testSyncFunction.mock.calls[4][0]).toBe('Handler 3 Call 2'); - expect(testSyncFunction.mock.calls[5][0]).toBe('Handler 3 Call 3'); - expect(testSyncFunction.mock.calls[6][0]).toBe('Handler 2 Call 1'); - expect(testSyncFunction.mock.calls[7][0]).toBe('Handler 2 Call 2'); - expect(testSyncFunction.mock.calls[8][0]).toBe('Handler 2 Call 3'); - }); - test('asynchronous handlers simple', async () => { - const e = new EventBus({ - captureRejections: true, - }); - e.on('test-event-1', async () => { - await testAsyncFunction('Handler 1'); - }); - e.on('test-event-2', async () => { - await testAsyncFunction('Handler 2'); - }); - e.on('test-event-1', async () => { - await testAsyncFunction('Handler 3'); - }); - const result1 = await e.emitAsync('test-event-1'); - const result2 = await e.emitAsync('test-event-2'); - expect(result1).toBeTruthy(); - expect(result2).toBeTruthy(); - expect(testAsyncFunction.mock.calls[0][0]).toBe('Handler 1'); - expect(testAsyncFunction.mock.calls[1][0]).toBe('Handler 3'); - expect(testAsyncFunction.mock.calls[2][0]).toBe('Handler 2'); - }); - test('asynchronous handlers complex', async () => { - const e = new EventBus({ - captureRejections: true, - }); - e.on('test-event-1', async () => { - await testAsyncFunction('Handler 1 Call 1'); - await testAsyncFunction('Handler 1 Call 2'); - await testAsyncFunction('Handler 1 Call 3'); - }); - e.on('test-event-2', async () => { - await testAsyncFunction('Handler 2 Call 1'); - await testAsyncFunction('Handler 2 Call 2'); - await testAsyncFunction('Handler 2 Call 3'); - }); - e.on('test-event-1', async () => { - await testAsyncFunction('Handler 3 Call 1'); - await testAsyncFunction('Handler 3 Call 2'); - await testAsyncFunction('Handler 3 Call 3'); - }); - const result1 = await e.emitAsync('test-event-1'); - const result2 = await e.emitAsync('test-event-2'); - expect(result1).toBeTruthy(); - expect(result2).toBeTruthy(); - expect(testAsyncFunction.mock.calls[0][0]).toBe('Handler 1 Call 1'); - expect(testAsyncFunction.mock.calls[1][0]).toBe('Handler 1 Call 2'); - expect(testAsyncFunction.mock.calls[2][0]).toBe('Handler 1 Call 3'); - expect(testAsyncFunction.mock.calls[3][0]).toBe('Handler 3 Call 1'); - expect(testAsyncFunction.mock.calls[4][0]).toBe('Handler 3 Call 2'); - expect(testAsyncFunction.mock.calls[5][0]).toBe('Handler 3 Call 3'); - expect(testAsyncFunction.mock.calls[6][0]).toBe('Handler 2 Call 1'); - expect(testAsyncFunction.mock.calls[7][0]).toBe('Handler 2 Call 2'); - expect(testAsyncFunction.mock.calls[8][0]).toBe('Handler 2 Call 3'); - }); - test('mixed handlers simple', async () => { - const e = new EventBus({ - captureRejections: true, - }); - e.on('test-event', () => { - testSyncFunction('Handler 1'); - }); - e.on('test-event', async () => { - await sleep(10); - testSyncFunction('Handler 2'); - }); - e.on('test-event', () => { - testSyncFunction('Handler 3'); - }); - const result = await e.emitAsync('test-event'); - expect(result).toBeTruthy(); - expect(testSyncFunction.mock.calls[0][0]).toBe('Handler 1'); - expect(testSyncFunction.mock.calls[1][0]).toBe('Handler 2'); - expect(testSyncFunction.mock.calls[2][0]).toBe('Handler 3'); - }); - test('mixed handlers complex', async () => { - const e = new EventBus({ - captureRejections: true, - }); - e.on('test-event', () => { - testSyncFunction('Handler 1 Call 1'); - testSyncFunction('Handler 1 Call 2'); - testSyncFunction('Handler 1 Call 3'); - }); - e.on('test-event', async () => { - await sleep(10); - testSyncFunction('Handler 2 Call 1'); - await sleep(10); - testSyncFunction('Handler 2 Call 2'); - await sleep(10); - testSyncFunction('Handler 2 Call 3'); - }); - e.on('test-event', () => { - testSyncFunction('Handler 3 Call 1'); - testSyncFunction('Handler 3 Call 2'); - testSyncFunction('Handler 3 Call 3'); - }); - const result = await e.emitAsync('test-event'); - expect(result).toBeTruthy(); - expect(testSyncFunction.mock.calls[0][0]).toBe('Handler 1 Call 1'); - expect(testSyncFunction.mock.calls[1][0]).toBe('Handler 1 Call 2'); - expect(testSyncFunction.mock.calls[2][0]).toBe('Handler 1 Call 3'); - expect(testSyncFunction.mock.calls[3][0]).toBe('Handler 2 Call 1'); - expect(testSyncFunction.mock.calls[4][0]).toBe('Handler 2 Call 2'); - expect(testSyncFunction.mock.calls[5][0]).toBe('Handler 2 Call 3'); - expect(testSyncFunction.mock.calls[6][0]).toBe('Handler 3 Call 1'); - expect(testSyncFunction.mock.calls[7][0]).toBe('Handler 3 Call 2'); - expect(testSyncFunction.mock.calls[8][0]).toBe('Handler 3 Call 3'); - }); - test('error handling', async () => { - const e = new EventBus({ - captureRejections: true, - }); - e.on('error', (e) => { - testSyncFunction(e.message); - }); - e.on('test-event-1', async () => { - await testAsyncFunction('Handler 1'); - }); - e.on('test-event-2', async () => { - await testAsyncFunction('Handler 2'); - }); - e.on('test-event-1', async () => { - throw new Error('Error Handler 3'); - }); - e.on('test-event-1', async () => { - await testAsyncFunction('Handler 4'); - }); - e.on('test-event-2', async () => { - await testAsyncFunction('Handler 5'); - }); - const result1 = await e.emitAsync('test-event-1'); - const result2 = await e.emitAsync('test-event-2'); - expect(result1).toBeTruthy(); - expect(result2).toBeTruthy(); - expect(testSyncFunction.mock.calls).toHaveLength(1); - expect(testAsyncFunction.mock.calls).toHaveLength(3); - expect(testSyncFunction.mock.calls[0][0]).toBe('Error Handler 3'); - expect(testAsyncFunction.mock.calls[0][0]).toBe('Handler 1'); - expect(testAsyncFunction.mock.calls[1][0]).toBe('Handler 2'); - expect(testAsyncFunction.mock.calls[2][0]).toBe('Handler 5'); - }); - test('error handling and catching', async () => { - const e = new EventBus({ - captureRejections: true, - }); - e.on('error', (e) => { - testSyncFunction(e.message); - }); - e.on('test-event', async () => { - await testAsyncFunction('Handler 1 Call 1'); - await testAsyncFunction('Handler 1 Call 2'); - }); - e.on('test-event', async () => { - try { - await testAsyncFunction('Handler 2 Call 1'); - throw new Error('Error Handler 2'); - } catch (e) { - await testAsyncFunction(e.message); - } - }); - e.on('test-event', async () => { - await testAsyncFunction('Handler 3'); - }); - const result = await e.emitAsync('test-event'); - expect(result).toBeTruthy(); - expect(testSyncFunction.mock.calls).toHaveLength(0); - expect(testAsyncFunction.mock.calls).toHaveLength(5); - expect(testAsyncFunction.mock.calls[0][0]).toBe('Handler 1 Call 1'); - expect(testAsyncFunction.mock.calls[1][0]).toBe('Handler 1 Call 2'); - expect(testAsyncFunction.mock.calls[2][0]).toBe('Handler 2 Call 1'); - expect(testAsyncFunction.mock.calls[3][0]).toBe('Error Handler 2'); - expect(testAsyncFunction.mock.calls[4][0]).toBe('Handler 3'); - }); - test('error handling using symbol', async () => { - const e = new EventBus({ - captureRejections: true, - }); - e[captureRejectionSymbol] = (err, event) => { - testSyncFunction(err + ' - ' + event); - }; - e.on('test-event-1', async () => { - await testAsyncFunction('Handler 1'); - }); - e.on('test-event-2', async () => { - await testAsyncFunction('Handler 2'); - }); - e.on('test-event-1', async () => { - throw new Error('Error Handler 3'); - }); - e.on('test-event-1', async () => { - await testAsyncFunction('Handler 4'); - }); - e.on('test-event-2', async () => { - await testAsyncFunction('Handler 5'); - }); - const result1 = await e.emitAsync('test-event-1'); - const result2 = await e.emitAsync('test-event-2'); - expect(result1).toBeTruthy(); - expect(result2).toBeTruthy(); - expect(testSyncFunction.mock.calls).toHaveLength(1); - expect(testAsyncFunction.mock.calls).toHaveLength(3); - expect(testSyncFunction.mock.calls[0][0]).toBe( - 'Error: Error Handler 3 - test-event-1', - ); - expect(testAsyncFunction.mock.calls[0][0]).toBe('Handler 1'); - expect(testAsyncFunction.mock.calls[1][0]).toBe('Handler 2'); - expect(testAsyncFunction.mock.calls[2][0]).toBe('Handler 5'); - }); -}); diff --git a/tests/gestalts/GestaltGraph.test.ts b/tests/gestalts/GestaltGraph.test.ts index 53ad6eb92..7571c77d0 100644 --- a/tests/gestalts/GestaltGraph.test.ts +++ b/tests/gestalts/GestaltGraph.test.ts @@ -384,9 +384,8 @@ describe('GestaltGraph', () => { }); try { // Setting - const [type, providerIdentityId] = await gestaltGraph.setIdentity( - gestaltIdentityInfo, - ); + const [type, providerIdentityId] = + await gestaltGraph.setIdentity(gestaltIdentityInfo); expect(type).toBe('identity'); expect(providerIdentityId[0]).toBe(gestaltIdentityInfo.providerId); expect(providerIdentityId[1]).toBe(gestaltIdentityInfo.identityId); @@ -416,9 +415,8 @@ describe('GestaltGraph', () => { }); try { // Setting - const [type, providerIdentityId] = await gestaltGraph.setIdentity( - gestaltIdentityInfo, - ); + const [type, providerIdentityId] = + await gestaltGraph.setIdentity(gestaltIdentityInfo); expect(type).toBe('identity'); expect(providerIdentityId[0]).toBe(gestaltIdentityInfo.providerId); expect(providerIdentityId[1]).toBe(gestaltIdentityInfo.identityId); @@ -682,9 +680,8 @@ describe('GestaltGraph', () => { 'identity', providerIdentitiyId, ]); - const gestalt = await gestaltGraph.getGestaltByIdentity( - providerIdentitiyId, - ); + const gestalt = + await gestaltGraph.getGestaltByIdentity(providerIdentitiyId); const gestaltIdentityId = encodeGestaltIdentityId([ 'identity', providerIdentitiyId, diff --git a/tests/keys/utils/generate.test.ts b/tests/keys/utils/generate.test.ts index 4e45ac001..64706bc8c 100644 --- a/tests/keys/utils/generate.test.ts +++ b/tests/keys/utils/generate.test.ts @@ -43,18 +43,16 @@ describe('keys/utils/generate', () => { const recoveryCode1 = recoveryCode.generateRecoveryCode( length as 12 | 24 | undefined, ); - const keyPair1 = await generate.generateDeterministicKeyPair( - recoveryCode1, - ); + const keyPair1 = + await generate.generateDeterministicKeyPair(recoveryCode1); expect(keyPair1.publicKey).toHaveLength(32); expect(keyPair1.privateKey).toHaveLength(32); expect(keyPair1.publicKey).not.toEqual(keyPair1.privateKey); expect(keyPair1.secretKey).toStrictEqual( Buffer.concat([keyPair1.privateKey, keyPair1.publicKey]), ); - const keyPair2 = await generate.generateDeterministicKeyPair( - recoveryCode1, - ); + const keyPair2 = + await generate.generateDeterministicKeyPair(recoveryCode1); expect(keyPair2.publicKey).toHaveLength(32); expect(keyPair2.privateKey).toHaveLength(32); expect(keyPair2.publicKey).not.toEqual(keyPair2.privateKey); diff --git a/tests/nodes/NodeConnection.test.ts b/tests/nodes/NodeConnection.test.ts index bc3c2ecf2..98069fe18 100644 --- a/tests/nodes/NodeConnection.test.ts +++ b/tests/nodes/NodeConnection.test.ts @@ -1,19 +1,19 @@ import type { Host, Port, TLSConfig } from '@/network/types'; -import type * as quicEvents from '@matrixai/quic/dist/events'; import type { NodeId, NodeIdEncoded } from '@/ids'; import type { RPCStream } from '@/rpc/types'; -import type { CertificatePEM } from '@/keys/types'; -import { QUICServer, QUICSocket } from '@matrixai/quic'; +import { QUICServer, QUICSocket, events as quicEvents } from '@matrixai/quic'; import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; import { errors as quicErrors } from '@matrixai/quic'; import { ErrorContextsTimedTimeOut } from '@matrixai/contexts/dist/errors'; import * as nodesUtils from '@/nodes/utils'; +import * as nodesEvents from '@/nodes/events'; import * as keysUtils from '@/keys/utils'; import RPCServer from '@/rpc/RPCServer'; import NodeConnection from '@/nodes/NodeConnection'; -import { never, promise } from '@/utils'; +import { promise } from '@/utils'; import * as networkUtils from '@/network/utils'; import * as tlsTestUtils from '../utils/tls'; +import * as testsUtils from '../utils/utils'; describe(`${NodeConnection.name}`, () => { const logger = new Logger(`${NodeConnection.name} test`, LogLevel.WARN, [ @@ -44,29 +44,15 @@ describe(`${NodeConnection.name}`, () => { return nc; }; - const handleStream = async (event: quicEvents.QUICConnectionStreamEvent) => { + const handleEventQUICConnectionStream = async ( + event: quicEvents.EventQUICConnectionStream, + ) => { // Streams are handled via the RPCServer. logger.info('Handling stream'); const stream = event.detail; rpcServer.handleStream(stream); }; - const handleConnection = async ( - event: quicEvents.QUICServerConnectionEvent, - ) => { - // Needs to setup stream handler - const conn = event.detail; - logger.info('Setting up stream handling for connection'); - conn.addEventListener('connectionStream', handleStream); - conn.addEventListener( - 'connectionStop', - () => { - conn.removeEventListener('connectionStream', handleStream); - }, - { once: true }, - ); - }; - beforeEach(async () => { const serverKeyPair = keysUtils.generateKeyPair(); const clientKeyPair = keysUtils.generateKeyPair(); @@ -86,9 +72,8 @@ describe(`${NodeConnection.name}`, () => { key: serverTlsConfig.keyPrivatePem, cert: serverTlsConfig.certChainPem, verifyPeer: true, - verifyAllowFail: true, + verifyCallback: networkUtils.verifyClientCertificateChain, }, - verifyCallback: networkUtils.verifyClientCertificateChain, crypto: { key: keysUtils.generateKey(), ops: crypto, @@ -105,11 +90,17 @@ describe(`${NodeConnection.name}`, () => { }); // Setting up handling logger.info('Setting up connection handling for server'); - quicServer.addEventListener('serverConnection', handleConnection); quicServer.addEventListener( - 'serverStop', + quicEvents.EventQUICConnectionStream.name, + handleEventQUICConnectionStream, + ); + quicServer.addEventListener( + quicEvents.EventQUICServerStopped.name, () => { - quicServer.removeEventListener('serverConnection', handleConnection); + quicServer.removeEventListener( + quicEvents.EventQUICConnectionStream.name, + handleEventQUICConnectionStream, + ); }, { once: true }, ); @@ -126,17 +117,16 @@ describe(`${NodeConnection.name}`, () => { afterEach(async () => { await Promise.all(nodeConnections.map((nc) => nc.destroy({ force: true }))); await clientSocket.stop({ force: true }); - await rpcServer.destroy(true); + await rpcServer.destroy({ force: true }); await quicServer.stop({ force: true }); // Ignore errors due to socket already stopped await serverSocket.stop({ force: true }); }); test('session readiness', async () => { const nodeConnection = await NodeConnection.createNodeConnection({ - handleStream: () => {}, targetNodeIds: [serverNodeId], targetHost: localHost as Host, - targetPort: quicServer.port as Port, + targetPort: quicServer.port as unknown as Port, manifest: {}, tlsConfig: clientTlsConfig, crypto, @@ -149,10 +139,9 @@ describe(`${NodeConnection.name}`, () => { }); test('connects to the target', async () => { await NodeConnection.createNodeConnection({ - handleStream: () => {}, targetNodeIds: [serverNodeId], targetHost: localHost as Host, - targetPort: quicServer.port as Port, + targetPort: quicServer.port as unknown as Port, manifest: {}, tlsConfig: clientTlsConfig, crypto, @@ -163,12 +152,11 @@ describe(`${NodeConnection.name}`, () => { test('connection fails to target (times out)', async () => { const nodeConnectionProm = NodeConnection.createNodeConnection( { - handleStream: () => {}, targetNodeIds: [serverNodeId], targetHost: localHost as Host, targetPort: 12345 as Port, manifest: {}, - connectionMaxIdleTimeout: 1000, + connectionKeepAliveTimeoutTime: 1000, tlsConfig: clientTlsConfig, crypto, quicSocket: clientSocket, @@ -184,12 +172,11 @@ describe(`${NodeConnection.name}`, () => { test('connection drops out (socket stops responding)', async () => { const nodeConnection = await NodeConnection.createNodeConnection( { - handleStream: () => {}, targetNodeIds: [serverNodeId], targetHost: localHost as Host, - targetPort: quicServer.port as Port, + targetPort: quicServer.port as unknown as Port, manifest: {}, - connectionMaxIdleTimeout: 100, + connectionKeepAliveTimeoutTime: 100, tlsConfig: clientTlsConfig, crypto, quicSocket: clientSocket, @@ -197,25 +184,19 @@ describe(`${NodeConnection.name}`, () => { }, { timer: 100 }, ).then(extractNodeConnection); - const destroyProm = promise(); - nodeConnection.addEventListener( - 'destroy', - () => { - destroyProm.resolveP(); - }, - { once: true }, + const destroyProm = testsUtils.promFromEvent( + nodeConnection, + nodesEvents.EventNodeConnectionDestroyed, ); - // NodeConnection. await serverSocket.stop({ force: true }); // Wait for destruction, may take 2+ seconds await destroyProm.p; }); test('get the root chain cert', async () => { const nodeConnection = await NodeConnection.createNodeConnection({ - handleStream: () => {}, targetNodeIds: [serverNodeId], targetHost: localHost as Host, - targetPort: quicServer.port as Port, + targetPort: quicServer.port as unknown as Port, manifest: {}, tlsConfig: clientTlsConfig, crypto, @@ -227,10 +208,9 @@ describe(`${NodeConnection.name}`, () => { }); test('get the NodeId', async () => { const nodeConnection = await NodeConnection.createNodeConnection({ - handleStream: () => {}, targetNodeIds: [serverNodeId], targetHost: localHost as Host, - targetPort: quicServer.port as Port, + targetPort: quicServer.port as unknown as Port, manifest: {}, tlsConfig: clientTlsConfig, crypto, @@ -241,12 +221,13 @@ describe(`${NodeConnection.name}`, () => { nodesUtils.encodeNodeId(nodeConnection.nodeId), ); }); + test('Should fail due to server rejecting client certificate (no certs)', async () => { - const nodeConnectionProm = NodeConnection.createNodeConnection({ + const nodeConnection = await NodeConnection.createNodeConnection({ handleStream: () => {}, targetNodeIds: [serverNodeId], targetHost: localHost as Host, - targetPort: quicServer.port as Port, + targetPort: quicServer.port as unknown as Port, manifest: {}, // @ts-ignore: TLS not used for this test tlsConfig: {}, @@ -254,16 +235,25 @@ describe(`${NodeConnection.name}`, () => { quicSocket: clientSocket, logger: logger.getChild(`${NodeConnection.name}`), }).then(extractNodeConnection); - await expect(nodeConnectionProm).rejects.toThrow( - quicErrors.ErrorQUICConnectionInternal, + const destroyProm = testsUtils.promFromEvent( + nodeConnection, + nodesEvents.EventNodeConnectionDestroyed, + ); + const errorProm = testsUtils.promFromEvent( + nodeConnection, + nodesEvents.EventNodeConnectionError, + ); + await destroyProm.p; + const evt = await errorProm.p; + expect(evt.detail.cause).toBeInstanceOf( + quicErrors.ErrorQUICConnectionPeerTLS, ); }); test('Should fail due to client rejecting server certificate (missing NodeId)', async () => { const nodeConnectionProm = NodeConnection.createNodeConnection({ - handleStream: () => {}, targetNodeIds: [clientNodeId], targetHost: localHost as Host, - targetPort: quicServer.port as Port, + targetPort: quicServer.port as unknown as Port, manifest: {}, tlsConfig: clientTlsConfig, crypto, @@ -275,13 +265,12 @@ describe(`${NodeConnection.name}`, () => { test('Should fail and destroy due to connection failure', async () => { const nodeConnection = await NodeConnection.createNodeConnection( { - handleStream: () => {}, targetNodeIds: [serverNodeId], targetHost: localHost as Host, - targetPort: quicServer.port as Port, + targetPort: quicServer.port as unknown as Port, manifest: {}, connectionKeepAliveIntervalTime: 100, - connectionMaxIdleTimeout: 200, + connectionKeepAliveTimeoutTime: 200, tlsConfig: clientTlsConfig, crypto, quicSocket: clientSocket, @@ -289,22 +278,21 @@ describe(`${NodeConnection.name}`, () => { }, { timer: 150 }, ).then(extractNodeConnection); - const destroyProm = promise(); - nodeConnection.addEventListener('destroy', () => { - destroyProm.resolveP(); - }); + const destroyProm = testsUtils.promFromEvent( + nodeConnection, + nodesEvents.EventNodeConnectionDestroyed, + ); await serverSocket.stop({ force: true }); await destroyProm.p; }); test('Should fail and destroy due to connection ending local', async () => { const nodeConnection = await NodeConnection.createNodeConnection( { - handleStream: () => {}, targetNodeIds: [serverNodeId], targetHost: localHost as Host, - targetPort: quicServer.port as Port, + targetPort: quicServer.port as unknown as Port, manifest: {}, - connectionMaxIdleTimeout: 200, + connectionKeepAliveTimeoutTime: 200, connectionKeepAliveIntervalTime: 100, tlsConfig: clientTlsConfig, crypto, @@ -313,12 +301,12 @@ describe(`${NodeConnection.name}`, () => { }, { timer: 150 }, ).then(extractNodeConnection); - const destroyProm = promise(); - nodeConnection.addEventListener('destroy', () => { - destroyProm.resolveP(); - }); + const destroyProm = testsUtils.promFromEvent( + nodeConnection, + nodesEvents.EventNodeConnectionDestroyed, + ); await nodeConnection.quicConnection.stop({ - applicationError: true, + isApp: true, errorCode: 0, force: false, }); @@ -327,12 +315,11 @@ describe(`${NodeConnection.name}`, () => { test('Should fail and destroy due to connection ending remote', async () => { const nodeConnection = await NodeConnection.createNodeConnection( { - handleStream: () => {}, targetNodeIds: [serverNodeId], targetHost: localHost as Host, - targetPort: quicServer.port as Port, + targetPort: quicServer.port as unknown as Port, manifest: {}, - connectionMaxIdleTimeout: 200, + connectionKeepAliveTimeoutTime: 200, connectionKeepAliveIntervalTime: 100, tlsConfig: clientTlsConfig, crypto, @@ -341,13 +328,15 @@ describe(`${NodeConnection.name}`, () => { }, { timer: 150 }, ).then(extractNodeConnection); - const destroyProm = promise(); - nodeConnection.addEventListener('destroy', () => { - destroyProm.resolveP(); - }); - serverSocket.connectionMap.forEach((connection) => { + const destroyProm = testsUtils.promFromEvent( + nodeConnection, + nodesEvents.EventNodeConnectionDestroyed, + ); + // @ts-ignore: kidnap internal property + const connectionMap = serverSocket.connectionMap; + connectionMap.forEach((connection) => { void connection.stop({ - applicationError: true, + isApp: true, errorCode: 0, force: false, }); @@ -356,22 +345,19 @@ describe(`${NodeConnection.name}`, () => { }); test('should wrap reverse connection', async () => { const nodeConnectionReverseProm = promise>(); - quicServer.removeEventListener('serverConnection', handleConnection); + quicServer.removeEventListener( + quicEvents.EventQUICConnectionStream.name, + handleEventQUICConnectionStream, + ); quicServer.addEventListener( - 'serverConnection', - async (event: quicEvents.QUICServerConnectionEvent) => { + quicEvents.EventQUICServerConnection.name, + async (event: quicEvents.EventQUICServerConnection) => { const quicConnection = event.detail; - const certChain = quicConnection.getRemoteCertsChain().map((pem) => { - const cert = keysUtils.certFromPEM(pem as CertificatePEM); - if (cert == null) never(); - return cert; - }); - if (certChain == null) never(); - const nodeId = keysUtils.certNodeId(certChain[0]); - if (nodeId == null) never(); + const { nodeId, certChain } = nodesUtils.parseRemoteCertsChain( + quicConnection.getRemoteCertsChain(), + ); const nodeConnection = await NodeConnection.createNodeConnectionReverse( { - handleStream: () => {}, nodeId, certChain, manifest: {}, @@ -384,47 +370,39 @@ describe(`${NodeConnection.name}`, () => { { once: true }, ); const nodeConnection = await NodeConnection.createNodeConnection({ - handleStream: () => {}, targetNodeIds: [serverNodeId], targetHost: localHost as Host, - targetPort: quicServer.port as Port, + targetPort: quicServer.port as unknown as Port, manifest: {}, tlsConfig: clientTlsConfig, crypto, quicSocket: clientSocket, logger: logger.getChild(`${NodeConnection.name}`), }).then(extractNodeConnection); - const nodeConnectionReverse = await nodeConnectionReverseProm.p; - const nodeConnectionDestroyProm = promise(); - nodeConnection.addEventListener( - 'destroy', - () => nodeConnectionDestroyProm.resolveP(), - { once: true }, + const destroyProm = testsUtils.promFromEvent( + nodeConnection, + nodesEvents.EventNodeConnectionDestroyed, ); + const nodeConnectionReverse = await nodeConnectionReverseProm.p; await nodeConnectionReverse.destroy({ force: true }); - await nodeConnectionDestroyProm.p; + await destroyProm.p; }); test('should handle reverse streams', async () => { const nodeConnectionReverseProm = promise>(); const reverseStreamProm = promise>(); - quicServer.removeEventListener('serverConnection', handleConnection); + quicServer.removeEventListener( + quicEvents.EventQUICConnectionStream.name, + handleEventQUICConnectionStream, + ); quicServer.addEventListener( - 'serverConnection', - async (event: quicEvents.QUICServerConnectionEvent) => { + quicEvents.EventQUICServerConnection.name, + async (event: quicEvents.EventQUICServerConnection) => { const quicConnection = event.detail; - const certChain = quicConnection.getRemoteCertsChain().map((pem) => { - const cert = keysUtils.certFromPEM(pem as CertificatePEM); - if (cert == null) never(); - return cert; - }); - if (certChain == null) never(); - const nodeId = keysUtils.certNodeId(certChain[0]); - if (nodeId == null) never(); + const { nodeId, certChain } = nodesUtils.parseRemoteCertsChain( + quicConnection.getRemoteCertsChain(), + ); const nodeConnection = await NodeConnection.createNodeConnectionReverse( { - handleStream: (stream) => { - reverseStreamProm.resolveP(stream); - }, nodeId, certChain, manifest: {}, @@ -432,43 +410,53 @@ describe(`${NodeConnection.name}`, () => { logger, }, ).then(extractNodeConnection); + nodeConnection.addEventListener( + nodesEvents.EventNodeConnectionStream.name, + (e: nodesEvents.EventNodeConnectionStream) => { + reverseStreamProm.resolveP(e.detail); + }, + { once: true }, + ); nodeConnectionReverseProm.resolveP(nodeConnection); }, { once: true }, ); const forwardStreamProm = promise>(); const nodeConnection = await NodeConnection.createNodeConnection({ - handleStream: (stream) => forwardStreamProm.resolveP(stream), targetNodeIds: [serverNodeId], targetHost: localHost as Host, - targetPort: quicServer.port as Port, + targetPort: quicServer.port as unknown as Port, manifest: {}, tlsConfig: clientTlsConfig, crypto, quicSocket: clientSocket, logger: logger.getChild(`${NodeConnection.name}`), }).then(extractNodeConnection); + nodeConnection.addEventListener( + nodesEvents.EventNodeConnectionStream.name, + (e: nodesEvents.EventNodeConnectionStream) => { + forwardStreamProm.resolveP(e.detail); + }, + { once: true }, + ); const nodeConnectionReverse = await nodeConnectionReverseProm.p; // Checking stream creation - const forwardStream = await nodeConnection.quicConnection.streamNew(); + const forwardStream = nodeConnection.quicConnection.newStream(); const writer1 = forwardStream.writable.getWriter(); await writer1.write(Buffer.from('Hello!')); await reverseStreamProm.p; - const reverseStream = - await nodeConnectionReverse.quicConnection.streamNew(); + const reverseStream = nodeConnectionReverse.quicConnection.newStream(); const writer2 = reverseStream.writable.getWriter(); await writer2.write(Buffer.from('Hello!')); await forwardStreamProm.p; - const nodeConnectionDestroyProm = promise(); - nodeConnection.addEventListener( - 'destroy', - () => nodeConnectionDestroyProm.resolveP(), - { once: true }, + const destroyProm = testsUtils.promFromEvent( + nodeConnection, + nodesEvents.EventNodeConnectionDestroyed, ); await nodeConnectionReverse.destroy({ force: true }); - await nodeConnectionDestroyProm.p; + await destroyProm.p; }); }); diff --git a/tests/nodes/NodeConnectionManager.general.test.ts b/tests/nodes/NodeConnectionManager.general.test.ts index 43da9bc1d..272de3dae 100644 --- a/tests/nodes/NodeConnectionManager.general.test.ts +++ b/tests/nodes/NodeConnectionManager.general.test.ts @@ -5,7 +5,6 @@ import fs from 'fs'; import path from 'path'; import os from 'os'; import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; -import { QUICSocket } from '@matrixai/quic'; import { IdInternal } from '@matrixai/id'; import { DB } from '@matrixai/db'; import { PromiseCancellable } from '@matrixai/async-cancellable'; @@ -32,7 +31,6 @@ describe(`${NodeConnectionManager.name} general test`, () => { ]); const localHost = '127.0.0.1'; const password = 'password'; - const crypto = tlsTestUtils.createCrypto(); let tlsConfig: TLSConfig; @@ -80,7 +78,6 @@ describe(`${NodeConnectionManager.name} general test`, () => { let serverAddress: NodeAddress; let serverNodeId: NodeId; let serverNodeIdEncoded: NodeIdEncoded; - let clientSocket: QUICSocket; let keyRing: KeyRing; let db: DB; @@ -93,7 +90,6 @@ describe(`${NodeConnectionManager.name} general test`, () => { let nodeConnectionManager: NodeConnectionManager; // Default stream handler, just drop the stream - const handleStream = () => {}; beforeEach(async () => { dataDir = await fs.promises.mkdtemp( @@ -103,37 +99,33 @@ describe(`${NodeConnectionManager.name} general test`, () => { // Setting up remote node const nodePath = path.join(dataDir, 'agentA'); remotePolykeyAgent = await PolykeyAgent.createPolykeyAgent({ - nodePath, password, - networkConfig: { - agentHost: localHost, - }, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath, + agentServiceHost: localHost, + clientServiceHost: localHost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, logger: logger.getChild('AgentA'), }); serverNodeId = remotePolykeyAgent.keyRing.getNodeId(); serverNodeIdEncoded = nodesUtils.encodeNodeId(serverNodeId); - clientSocket = new QUICSocket({ - logger: logger.getChild('clientSocket'), - }); - await clientSocket.start({ - host: localHost, - }); - // Setting up client dependencies const keysPath = path.join(dataDir, 'keys'); keyRing = await KeyRing.createKeyRing({ password, keysPath, logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }); tlsConfig = await tlsTestUtils.createTLSConfig(keyRing.keyPair); const dbPath = path.join(dataDir, 'db'); @@ -165,15 +157,17 @@ describe(`${NodeConnectionManager.name} general test`, () => { logger, }); serverAddress = { - host: remotePolykeyAgent.quicSocket.host as Host, - port: remotePolykeyAgent.quicSocket.port as Port, + host: remotePolykeyAgent.nodeConnectionManager.host as Host, + port: remotePolykeyAgent.nodeConnectionManager.port as Port, }; }); afterEach(async () => { logger.info('AFTER EACH'); - await nodeConnectionManager?.stop(); + await taskManager.stopProcessing(); await taskManager.stopTasks(); + await nodeManager?.stop(); + await nodeConnectionManager?.stop(); await sigchain.stop(); await sigchain.destroy(); await nodeGraph.stop(); @@ -186,7 +180,6 @@ describe(`${NodeConnectionManager.name} general test`, () => { await db.destroy(); await keyRing.stop(); await keyRing.destroy(); - await clientSocket.stop({ force: true }); await taskManager.stop(); await remotePolykeyAgent.stop(); @@ -198,8 +191,6 @@ describe(`${NodeConnectionManager.name} general test`, () => { logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); nodeManager = new NodeManager({ @@ -214,8 +205,7 @@ describe(`${NodeConnectionManager.name} general test`, () => { }); await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); await taskManager.startProcessing(); @@ -238,11 +228,11 @@ describe(`${NodeConnectionManager.name} general test`, () => { keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, - connectionMaxIdleTimeout: 10000, - connectionKeepAliveIntervalTime: 1000, + options: { + connectionKeepAliveTimeoutTime: 10000, + connectionKeepAliveIntervalTime: 1000, + }, tlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); nodeManager = new NodeManager({ @@ -257,8 +247,7 @@ describe(`${NodeConnectionManager.name} general test`, () => { }); await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); await taskManager.startProcessing(); // Mocking pinging to always return true @@ -292,11 +281,11 @@ describe(`${NodeConnectionManager.name} general test`, () => { keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, - connectionMaxIdleTimeout: 10000, - connectionKeepAliveIntervalTime: 1000, + options: { + connectionKeepAliveTimeoutTime: 10000, + connectionKeepAliveIntervalTime: 1000, + }, tlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); nodeManager = new NodeManager({ @@ -311,8 +300,7 @@ describe(`${NodeConnectionManager.name} general test`, () => { }); await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); await taskManager.startProcessing(); // Mocking pinging to always return true @@ -339,11 +327,11 @@ describe(`${NodeConnectionManager.name} general test`, () => { keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, - connectionMaxIdleTimeout: 10000, - connectionKeepAliveIntervalTime: 1000, + options: { + connectionKeepAliveTimeoutTime: 10000, + connectionKeepAliveIntervalTime: 1000, + }, tlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); nodeManager = new NodeManager({ @@ -358,8 +346,7 @@ describe(`${NodeConnectionManager.name} general test`, () => { }); await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); await taskManager.startProcessing(); // Mocking pinging to always return true @@ -421,11 +408,11 @@ describe(`${NodeConnectionManager.name} general test`, () => { keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, - connectionMaxIdleTimeout: 10000, - connectionKeepAliveIntervalTime: 1000, + options: { + connectionKeepAliveTimeoutTime: 10000, + connectionKeepAliveIntervalTime: 1000, + }, tlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); nodeManager = new NodeManager({ @@ -440,8 +427,7 @@ describe(`${NodeConnectionManager.name} general test`, () => { }); await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); await taskManager.startProcessing(); // Mocking pinging to always return true @@ -503,11 +489,11 @@ describe(`${NodeConnectionManager.name} general test`, () => { keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, - connectionMaxIdleTimeout: 10000, - connectionKeepAliveIntervalTime: 1000, + options: { + connectionKeepAliveTimeoutTime: 10000, + connectionKeepAliveIntervalTime: 1000, + }, tlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); nodeManager = new NodeManager({ @@ -522,8 +508,7 @@ describe(`${NodeConnectionManager.name} general test`, () => { }); await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); await taskManager.startProcessing(); @@ -562,72 +547,5 @@ describe(`${NodeConnectionManager.name} general test`, () => { await nodeConnectionManager.stop(); }); - test('getClosestGlobalNodes should skip recent offline nodes', async () => { - nodeConnectionManager = new NodeConnectionManager({ - keyRing, - logger: logger.getChild(NodeConnectionManager.name), - nodeGraph, - connectionMaxIdleTimeout: 10000, - connectionKeepAliveIntervalTime: 1000, - tlsConfig, - crypto, - quicSocket: clientSocket, - seedNodes: undefined, - }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, - }); - await nodeManager.start(); - await nodeConnectionManager.start({ - nodeManager, - handleStream, - }); - await taskManager.startProcessing(); - - const randomNodeId1 = testNodesUtils.generateRandomNodeId(); - const randomNodeId2 = testNodesUtils.generateRandomNodeId(); - const randomNodeId3 = testNodesUtils.generateRandomNodeId(); - - // Add fake data to `NodeGraph` - await nodeGraph.setNode(randomNodeId1, serverAddress); - await nodeGraph.setNode(randomNodeId2, serverAddress); - - // Making pings fail - const mockedPingNode = jest.spyOn( - NodeConnectionManager.prototype, - 'pingNode', - ); - mockedPingNode.mockImplementation( - () => new PromiseCancellable((resolve) => resolve(false)), - ); - await nodeConnectionManager.getClosestGlobalNodes(randomNodeId3, false); - expect(mockedPingNode).toHaveBeenCalled(); - - // Nodes 1 and 2 should exist in backoff map - // @ts-ignore: kidnap protected property - const backoffMap = nodeConnectionManager.nodesBackoffMap; - expect(backoffMap.has(randomNodeId1.toString())).toBeTrue(); - expect(backoffMap.has(randomNodeId2.toString())).toBeTrue(); - expect(backoffMap.has(randomNodeId3.toString())).toBeFalse(); - - // Next find node should skip offline nodes - mockedPingNode.mockClear(); - await nodeConnectionManager.getClosestGlobalNodes(randomNodeId3, true); - expect(mockedPingNode).not.toHaveBeenCalled(); - - // We can try connecting anyway - mockedPingNode.mockClear(); - await nodeConnectionManager.getClosestGlobalNodes(randomNodeId3, false); - expect(mockedPingNode).toHaveBeenCalled(); - - await nodeConnectionManager.stop(); - }); test.todo('Handles reverse streams'); }); diff --git a/tests/nodes/NodeConnectionManager.lifecycle.test.ts b/tests/nodes/NodeConnectionManager.lifecycle.test.ts index 8cc9ddd7e..bb99d978d 100644 --- a/tests/nodes/NodeConnectionManager.lifecycle.test.ts +++ b/tests/nodes/NodeConnectionManager.lifecycle.test.ts @@ -5,24 +5,16 @@ import path from 'path'; import fs from 'fs'; import os from 'os'; import { DB } from '@matrixai/db'; -import { QUICServer, QUICSocket } from '@matrixai/quic'; import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; import KeyRing from '@/keys/KeyRing'; import NodeGraph from '@/nodes/NodeGraph'; -import NodeManager from '@/nodes/NodeManager'; -import ACL from '@/acl/ACL'; -import GestaltGraph from '@/gestalts/GestaltGraph'; -import Sigchain from '@/sigchain/Sigchain'; -import TaskManager from '@/tasks/TaskManager'; import * as nodesUtils from '@/nodes/utils'; import * as keysUtils from '@/keys/utils'; import NodeConnectionManager from '@/nodes/NodeConnectionManager'; import { promise, sleep } from '@/utils'; import * as nodesErrors from '@/nodes/errors'; -import NodeConnection from '../../src/nodes/NodeConnection'; -import RPCServer from '../../src/rpc/RPCServer'; +import NodeConnection from '@/nodes/NodeConnection'; import * as tlsUtils from '../utils/tls'; -import * as tlsTestUtils from '../utils/tls'; describe(`${NodeConnectionManager.name} lifecycle test`, () => { const logger = new Logger(`${NodeConnection.name} test`, LogLevel.WARN, [ @@ -30,9 +22,8 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, ), ]); - const localHost = '127.0.0.1'; + const localHost = '127.0.0.1' as Host; const password = 'password'; - const crypto = tlsTestUtils.createCrypto(); let dataDir: string; @@ -41,28 +32,21 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { let serverNodeId: NodeId; let clientNodeId: NodeId; let serverNodeIdEncoded: NodeIdEncoded; - let serverSocket: QUICSocket; - let quicServer: QUICServer; - let rpcServer: RPCServer; + let keyRingPeer: KeyRing; + let nodeConnectionManagerPeer: NodeConnectionManager; let serverAddress: NodeAddress; - let clientSocket: QUICSocket; let keyRing: KeyRing; let db: DB; - let acl: ACL; - let gestaltGraph: GestaltGraph; let nodeGraph: NodeGraph; - let sigchain: Sigchain; - let taskManager: TaskManager; - let nodeManager: NodeManager; let nodeConnectionManager: NodeConnectionManager; - const handleStream = () => {}; beforeEach(async () => { dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); + const keysPathPeer = path.join(dataDir, 'keysPeer'); const serverKeyPair = keysUtils.generateKeyPair(); const clientKeyPair = keysUtils.generateKeyPair(); serverNodeId = keysUtils.publicKeyToNodeId(serverKeyPair.publicKey); @@ -70,37 +54,24 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { serverNodeIdEncoded = nodesUtils.encodeNodeId(serverNodeId); serverTlsConfig = await tlsUtils.createTLSConfig(serverKeyPair); clientTlsConfig = await tlsUtils.createTLSConfig(clientKeyPair); - serverSocket = new QUICSocket({ - logger: logger.getChild('serverSocket'), - }); - await serverSocket.start({ - host: localHost, - }); - quicServer = new QUICServer({ - config: { - key: serverTlsConfig.keyPrivatePem, - cert: serverTlsConfig.certChainPem, - }, - crypto: { - key: keysUtils.generateKey(), - ops: crypto, + keyRingPeer = await KeyRing.createKeyRing({ + password, + keysPath: keysPathPeer, + logger, + options: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, }, - socket: serverSocket, - logger: logger.getChild(`${QUICServer.name}`), }); - rpcServer = await RPCServer.createRPCServer({ - handlerTimeoutGraceTime: 1000, - handlerTimeoutTime: 5000, - logger: logger.getChild(`${RPCServer.name}`), - manifest: {}, // TODO: test server manifest - sensitive: false, - }); - - await quicServer.start(); - clientSocket = new QUICSocket({ - logger: logger.getChild('clientSocket'), + nodeConnectionManagerPeer = new NodeConnectionManager({ + keyRing: keyRingPeer, + logger: logger.getChild(`${NodeConnectionManager.name}Peer`), + nodeGraph: {} as NodeGraph, + tlsConfig: serverTlsConfig, + seedNodes: undefined, }); - await clientSocket.start({ + await nodeConnectionManagerPeer.start({ host: localHost, }); @@ -110,123 +81,89 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { password, keysPath, logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }); 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, - }); nodeGraph = await NodeGraph.createNodeGraph({ db, keyRing, logger, }); - sigchain = await Sigchain.createSigchain({ - db, - keyRing, - logger, - }); - taskManager = await TaskManager.createTaskManager({ - db, - logger, - }); serverAddress = { - host: quicServer.host as Host, - port: quicServer.port as Port, + host: nodeConnectionManagerPeer.host, + port: nodeConnectionManagerPeer.port, }; }); afterEach(async () => { - await taskManager.stop(); - await nodeConnectionManager.stop(); - await sigchain.stop(); - await sigchain.destroy(); + await nodeConnectionManager?.stop(); await nodeGraph.stop(); await nodeGraph.destroy(); - await gestaltGraph.stop(); - await gestaltGraph.destroy(); - await acl.stop(); - await acl.destroy(); - await taskManager.destroy(); await db.stop(); await db.destroy(); await keyRing.stop(); await keyRing.destroy(); - await clientSocket.stop({ force: true }); - await rpcServer.destroy(true); - await quicServer.stop({ force: true }).catch(() => {}); // Ignore errors due to socket already stopped - await serverSocket.stop({ force: true }); + await nodeConnectionManagerPeer.stop(); }); - test('should create connection', async () => { + test('NodeConnectionManager readiness', async () => { nodeConnectionManager = new NodeConnectionManager({ keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, - crypto, tlsConfig: clientTlsConfig, - quicSocket: clientSocket, seedNodes: undefined, }); - nodeManager = new NodeManager({ - db, - gestaltGraph, + await nodeConnectionManager.start({ + host: localHost, + }); + + await nodeConnectionManager.stop(); + }); + test('NodeConnectionManager consecutive start stops', async () => { + nodeConnectionManager = new NodeConnectionManager({ keyRing, - nodeConnectionManager, + logger: logger.getChild(NodeConnectionManager.name), nodeGraph, - sigchain, - taskManager, - logger, + tlsConfig: clientTlsConfig, + seedNodes: undefined, }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost, }); await nodeConnectionManager.stop(); + await nodeConnectionManager.start({ + host: localHost, + }); + await nodeConnectionManager.stop(); }); + // FIXME: holding process open for a time. connectionKeepAliveIntervalTime holds the process open, failing to clean up? test('acquireConnection should create connection', async () => { await nodeGraph.setNode(serverNodeId, serverAddress); nodeConnectionManager = new NodeConnectionManager({ keyRing, - logger: logger.getChild(NodeConnectionManager.name), nodeGraph, + options: { + connectionConnectTimeoutTime: 1000, + }, + logger: logger.getChild(`${NodeConnectionManager.name}Local`), tlsConfig: clientTlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, - }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost, }); - await taskManager.startProcessing(); const acquire = await nodeConnectionManager.acquireConnection(serverNodeId); const [release] = await acquire(); @@ -241,26 +178,11 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig: clientTlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, - }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost, }); - await taskManager.startProcessing(); await nodeConnectionManager.withConnF(serverNodeId, async () => { expect(nodeConnectionManager.hasConnection(serverNodeId)).toBeTrue(); @@ -275,26 +197,11 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig: clientTlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, - }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost, }); - await taskManager.startProcessing(); await nodeConnectionManager.withConnF(serverNodeId, async () => { expect(nodeConnectionManager.hasConnection(serverNodeId)).toBeTrue(); @@ -305,8 +212,12 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { expect(nodesUtils.encodeNodeId(connectionsList[0].nodeId)).toEqual( serverNodeIdEncoded, ); - expect(connectionsList[0].address.host).toEqual(quicServer.host); - expect(connectionsList[0].address.port).toEqual(quicServer.port); + expect(connectionsList[0].address.host).toEqual( + nodeConnectionManagerPeer.host, + ); + expect(connectionsList[0].address.port).toEqual( + nodeConnectionManagerPeer.port, + ); await nodeConnectionManager.stop(); }); @@ -317,26 +228,11 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig: clientTlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, - }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost, }); - await taskManager.startProcessing(); // @ts-ignore: kidnap protected property const connectionMap = nodeConnectionManager.connections; @@ -359,26 +255,11 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig: clientTlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, - }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost, }); - await taskManager.startProcessing(); // @ts-ignore: kidnap protected property const connectionMap = nodeConnectionManager.connections; const randomNodeId = keysUtils.publicKeyToNodeId( @@ -409,26 +290,11 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig: clientTlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, - }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost, }); - await taskManager.startProcessing(); await nodeConnectionManager.withConnF(serverNodeId, async () => { // Do nothing }); @@ -449,26 +315,11 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig: clientTlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, - }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost, }); - await taskManager.startProcessing(); const waitProm = promise(); const tryConnection = () => { return nodeConnectionManager.withConnF(serverNodeId, async () => { @@ -497,26 +348,11 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig: clientTlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, - }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost, }); - await taskManager.startProcessing(); await nodeConnectionManager.withConnF(serverNodeId, async () => { // Do nothing }); @@ -544,26 +380,11 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig: clientTlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, - }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost, }); - await taskManager.startProcessing(); await nodeConnectionManager.withConnF(serverNodeId, async () => { // Do nothing }); @@ -583,30 +404,15 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig: clientTlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, - }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost, }); - await taskManager.startProcessing(); const result = await nodeConnectionManager.pingNode( serverNodeId, localHost as Host, - quicServer.port as Port, + nodeConnectionManagerPeer.port, ); expect(result).toBeTrue(); expect(nodeConnectionManager.hasConnection(serverNodeId)).toBeTrue(); @@ -619,67 +425,37 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig: clientTlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, - }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost, }); - await taskManager.startProcessing(); const result = await nodeConnectionManager.pingNode( serverNodeId, localHost as Host, 12345 as Port, + { timer: 100 }, ); expect(result).toBeFalse(); expect(nodeConnectionManager.hasConnection(serverNodeId)).toBeFalse(); await nodeConnectionManager.stop(); }); - // TODO: this needs the custom node verification logic to work. test('should fail to ping node if NodeId does not match', async () => { nodeConnectionManager = new NodeConnectionManager({ keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig: clientTlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, - }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost, }); - await taskManager.startProcessing(); const result = await nodeConnectionManager.pingNode( clientNodeId, localHost as Host, - quicServer.port as Port, + nodeConnectionManagerPeer.port, ); expect(result).toBeFalse(); expect(nodeConnectionManager.hasConnection(clientNodeId)).toBeFalse(); diff --git a/tests/nodes/NodeConnectionManager.seednodes.test.ts b/tests/nodes/NodeConnectionManager.seednodes.test.ts index e8e5759d0..a3ecfc8b6 100644 --- a/tests/nodes/NodeConnectionManager.seednodes.test.ts +++ b/tests/nodes/NodeConnectionManager.seednodes.test.ts @@ -1,12 +1,10 @@ import type { Host, Port, TLSConfig } from '@/network/types'; import type { NodeId, NodeIdEncoded } from '@/ids'; import type { NodeAddress } from '@/nodes/types'; -import type { SeedNodes } from '@/nodes/types'; import fs from 'fs'; import path from 'path'; import os from 'os'; import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; -import { QUICSocket } from '@matrixai/quic'; import { DB } from '@matrixai/db'; import { PromiseCancellable } from '@matrixai/async-cancellable'; import * as nodesUtils from '@/nodes/utils'; @@ -21,11 +19,12 @@ import Sigchain from '@/sigchain/Sigchain'; import TaskManager from '@/tasks/TaskManager'; import NodeManager from '@/nodes/NodeManager'; import PolykeyAgent from '@/PolykeyAgent'; +import * as utils from '@/utils'; import * as testNodesUtils from './utils'; import * as tlsTestUtils from '../utils/tls'; describe(`${NodeConnectionManager.name} seednodes test`, () => { - const logger = new Logger(`${NodeConnection.name} test`, LogLevel.WARN, [ + const logger = new Logger(`${NodeConnection.name} test`, LogLevel.INFO, [ new StreamHandler( formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, ), @@ -36,7 +35,6 @@ describe(`${NodeConnectionManager.name} seednodes test`, () => { port: 55555 as Port, }; const password = 'password'; - const crypto = tlsTestUtils.createCrypto(); function createPromiseCancellableNop() { return () => new PromiseCancellable((resolve) => resolve()); @@ -51,7 +49,6 @@ describe(`${NodeConnectionManager.name} seednodes test`, () => { let remoteNodeId1: NodeId; let remoteNodeId2: NodeId; let remoteNodeIdEncoded1: NodeIdEncoded; - let clientSocket: QUICSocket; let keyRing: KeyRing; let db: DB; @@ -60,9 +57,9 @@ describe(`${NodeConnectionManager.name} seednodes test`, () => { let nodeGraph: NodeGraph; let sigchain: Sigchain; let taskManager: TaskManager; + let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; let tlsConfig: TLSConfig; - const handleStream = () => {}; beforeEach(async () => { dataDir = await fs.promises.mkdtemp( @@ -72,61 +69,58 @@ describe(`${NodeConnectionManager.name} seednodes test`, () => { // Setting up remote node const nodePathA = path.join(dataDir, 'agentA'); remotePolykeyAgent1 = await PolykeyAgent.createPolykeyAgent({ - nodePath: nodePathA, password, - networkConfig: { - agentHost: localHost, + options: { + nodePath: nodePathA, + agentServiceHost: localHost, + clientServiceHost: localHost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }, - logger: logger.getChild('AgentA'), + logger: logger.getChild('Agent1'), }); remoteNodeId1 = remotePolykeyAgent1.keyRing.getNodeId(); remoteNodeIdEncoded1 = nodesUtils.encodeNodeId(remoteNodeId1); remoteAddress1 = { - host: remotePolykeyAgent1.quicSocket.host as Host, - port: remotePolykeyAgent1.quicSocket.port as Port, + host: remotePolykeyAgent1.nodeConnectionManager.host as Host, + port: remotePolykeyAgent1.nodeConnectionManager.port as Port, }; const nodePathB = path.join(dataDir, 'agentB'); remotePolykeyAgent2 = await PolykeyAgent.createPolykeyAgent({ - nodePath: nodePathB, password, - networkConfig: { - agentHost: localHost, - }, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath: nodePathB, + agentServiceHost: localHost, + clientServiceHost: localHost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, - logger: logger.getChild('AgentA'), + logger: logger.getChild('Agent2'), }); remoteNodeId2 = remotePolykeyAgent2.keyRing.getNodeId(); remoteAddress2 = { - host: remotePolykeyAgent2.quicSocket.host as Host, - port: remotePolykeyAgent2.quicSocket.port as Port, + host: remotePolykeyAgent2.nodeConnectionManager.host as Host, + port: remotePolykeyAgent2.nodeConnectionManager.port as Port, }; - clientSocket = new QUICSocket({ - logger: logger.getChild('clientSocket'), - }); - await clientSocket.start({ - host: localHost, - }); - // Setting up client dependencies const keysPath = path.join(dataDir, 'keys'); keyRing = await KeyRing.createKeyRing({ password, keysPath, logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }); const dbPath = path.join(dataDir, 'db'); db = await DB.createDB({ @@ -162,7 +156,10 @@ describe(`${NodeConnectionManager.name} seednodes test`, () => { }); afterEach(async () => { - await taskManager.stop(); + await taskManager.stopProcessing(); + await taskManager.stopTasks(); + await nodeManager?.stop(); + await nodeConnectionManager?.stop(); await sigchain.stop(); await sigchain.destroy(); await nodeGraph.stop(); @@ -171,76 +168,29 @@ describe(`${NodeConnectionManager.name} seednodes test`, () => { await gestaltGraph.destroy(); await acl.stop(); await acl.destroy(); - await taskManager.destroy(); + await taskManager.stop(); await db.stop(); await db.destroy(); await keyRing.stop(); await keyRing.destroy(); - await clientSocket.stop({ force: true }); await remotePolykeyAgent1.stop(); await remotePolykeyAgent2.stop(); }); - test('starting should add seed nodes to the node graph', async () => { - const nodeId1 = testNodesUtils.generateRandomNodeId(); - const nodeId2 = testNodesUtils.generateRandomNodeId(); - const nodeId3 = testNodesUtils.generateRandomNodeId(); - const dummySeedNodes: SeedNodes = { - [nodesUtils.encodeNodeId(nodeId1)]: testAddress, - [nodesUtils.encodeNodeId(nodeId2)]: testAddress, - [nodesUtils.encodeNodeId(nodeId3)]: testAddress, - }; - const nodeConnectionManager = new NodeConnectionManager({ - keyRing, - logger: logger.getChild(NodeConnectionManager.name), - nodeGraph, - tlsConfig, - crypto, - quicSocket: clientSocket, - seedNodes: dummySeedNodes, - }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, - }); - await nodeManager.start(); - await nodeConnectionManager.start({ - nodeManager, - handleStream, - }); - await taskManager.startProcessing(); - - const seedNodes = nodeConnectionManager.getSeedNodes(); - expect(seedNodes).toContainEqual(nodeId1); - expect(seedNodes).toContainEqual(nodeId2); - expect(seedNodes).toContainEqual(nodeId3); - expect(await nodeGraph.getNode(seedNodes[0])).toBeDefined(); - expect(await nodeGraph.getNode(seedNodes[1])).toBeDefined(); - expect(await nodeGraph.getNode(seedNodes[2])).toBeDefined(); - const dummyNodeId = testNodesUtils.generateRandomNodeId(); - expect(await nodeGraph.getNode(dummyNodeId)).toBeUndefined(); - - await nodeConnectionManager.stop(); - }); test('should synchronise nodeGraph', async () => { - const nodeConnectionManager = new NodeConnectionManager({ + const seedNodes = { + [remoteNodeIdEncoded1]: remoteAddress1, + }; + nodeConnectionManager = new NodeConnectionManager({ keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, - connectionKeepAliveIntervalTime: 1000, - tlsConfig, - crypto, - quicSocket: clientSocket, - seedNodes: { - [remoteNodeIdEncoded1]: remoteAddress1, + options: { + connectionKeepAliveIntervalTime: 1000, }, + tlsConfig, + seedNodes, }); nodeManager = new NodeManager({ db, @@ -253,13 +203,25 @@ describe(`${NodeConnectionManager.name} seednodes test`, () => { logger, }); await nodeManager.start(); + // Add seed nodes to the nodeGraph + const setNodeProms = new Array>(); + for (const nodeIdEncoded in seedNodes) { + const nodeId = nodesUtils.decodeNodeId(nodeIdEncoded); + if (nodeId == null) utils.never(); + const setNodeProm = nodeManager.setNode( + nodeId, + seedNodes[nodeIdEncoded], + true, + ); + setNodeProms.push(setNodeProm); + } + await Promise.all(setNodeProms); const dummyNodeId = testNodesUtils.generateRandomNodeId(); await remotePolykeyAgent1.nodeGraph.setNode(remoteNodeId2, remoteAddress2); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); await taskManager.startProcessing(); @@ -271,23 +233,24 @@ describe(`${NodeConnectionManager.name} seednodes test`, () => { await nodeConnectionManager.stop(); }); test('should call refreshBucket when syncing nodeGraph', async () => { + const seedNodes = { + [remoteNodeIdEncoded1]: remoteAddress1, + }; const mockedRefreshBucket = jest.spyOn( NodeManager.prototype, 'refreshBucket', ); mockedRefreshBucket.mockImplementation(createPromiseCancellableNop()); - const nodeConnectionManager = new NodeConnectionManager({ + nodeConnectionManager = new NodeConnectionManager({ keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, - connectionMaxIdleTimeout: 1000, - connectionKeepAliveIntervalTime: 500, - tlsConfig, - crypto, - quicSocket: clientSocket, - seedNodes: { - [remoteNodeIdEncoded1]: remoteAddress1, + options: { + connectionKeepAliveTimeoutTime: 1000, + connectionKeepAliveIntervalTime: 500, }, + tlsConfig, + seedNodes, }); nodeManager = new NodeManager({ db, @@ -300,9 +263,21 @@ describe(`${NodeConnectionManager.name} seednodes test`, () => { logger, }); await nodeManager.start(); + // Add seed nodes to the nodeGraph + const setNodeProms = new Array>(); + for (const nodeIdEncoded in seedNodes) { + const nodeId = nodesUtils.decodeNodeId(nodeIdEncoded); + if (nodeId == null) utils.never(); + const setNodeProm = nodeManager.setNode( + nodeId, + seedNodes[nodeIdEncoded], + true, + ); + setNodeProms.push(setNodeProm); + } + await Promise.all(setNodeProms); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); await taskManager.startProcessing(); @@ -325,17 +300,18 @@ describe(`${NodeConnectionManager.name} seednodes test`, () => { ); mockedRefreshBucket.mockImplementation(createPromiseCancellableNop()); - const nodeConnectionManager = new NodeConnectionManager({ + const seedNodes = { + [remoteNodeIdEncoded1]: remoteAddress1, + }; + nodeConnectionManager = new NodeConnectionManager({ keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, - connectionKeepAliveIntervalTime: 1000, - tlsConfig, - crypto, - quicSocket: clientSocket, - seedNodes: { - [remoteNodeIdEncoded1]: remoteAddress1, + options: { + connectionKeepAliveIntervalTime: 1000, }, + tlsConfig, + seedNodes, }); nodeManager = new NodeManager({ db, @@ -348,9 +324,21 @@ describe(`${NodeConnectionManager.name} seednodes test`, () => { logger, }); await nodeManager.start(); + // Add seed nodes to the nodeGraph + const setNodeProms = new Array>(); + for (const nodeIdEncoded in seedNodes) { + const nodeId = nodesUtils.decodeNodeId(nodeIdEncoded); + if (nodeId == null) utils.never(); + const setNodeProm = nodeManager.setNode( + nodeId, + seedNodes[nodeIdEncoded], + true, + ); + setNodeProms.push(setNodeProm); + } + await Promise.all(setNodeProms); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); await taskManager.startProcessing(); @@ -371,17 +359,18 @@ describe(`${NodeConnectionManager.name} seednodes test`, () => { ); mockedRefreshBucket.mockImplementation(createPromiseCancellableNop()); - const nodeConnectionManager = new NodeConnectionManager({ + const seedNodes = { + [remoteNodeIdEncoded1]: remoteAddress1, + }; + nodeConnectionManager = new NodeConnectionManager({ keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, - connectionKeepAliveIntervalTime: 1000, - tlsConfig, - crypto, - quicSocket: clientSocket, - seedNodes: { - [remoteNodeIdEncoded1]: remoteAddress1, + options: { + connectionKeepAliveIntervalTime: 1000, }, + tlsConfig, + seedNodes, }); nodeManager = new NodeManager({ db, @@ -394,9 +383,21 @@ describe(`${NodeConnectionManager.name} seednodes test`, () => { logger, }); await nodeManager.start(); + // Add seed nodes to the nodeGraph + const setNodeProms = new Array>(); + for (const nodeIdEncoded in seedNodes) { + const nodeId = nodesUtils.decodeNodeId(nodeIdEncoded); + if (nodeId == null) utils.never(); + const setNodeProm = nodeManager.setNode( + nodeId, + seedNodes[nodeIdEncoded], + true, + ); + setNodeProms.push(setNodeProm); + } + await Promise.all(setNodeProms); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); await taskManager.startProcessing(); @@ -412,17 +413,18 @@ describe(`${NodeConnectionManager.name} seednodes test`, () => { await nodeConnectionManager.stop(); }); test('refreshBucket delays should be reset after finding less than 20 nodes', async () => { - const nodeConnectionManager = new NodeConnectionManager({ + const seedNodes = { + [remoteNodeIdEncoded1]: remoteAddress1, + }; + nodeConnectionManager = new NodeConnectionManager({ keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, - connectionKeepAliveIntervalTime: 1000, - tlsConfig, - crypto, - quicSocket: clientSocket, - seedNodes: { - [remoteNodeIdEncoded1]: remoteAddress1, + options: { + connectionKeepAliveIntervalTime: 1000, }, + tlsConfig, + seedNodes, }); nodeManager = new NodeManager({ db, @@ -435,9 +437,21 @@ describe(`${NodeConnectionManager.name} seednodes test`, () => { logger, }); await nodeManager.start(); + // Add seed nodes to the nodeGraph + const setNodeProms = new Array>(); + for (const nodeIdEncoded in seedNodes) { + const nodeId = nodesUtils.decodeNodeId(nodeIdEncoded); + if (nodeId == null) utils.never(); + const setNodeProm = nodeManager.setNode( + nodeId, + seedNodes[nodeIdEncoded], + true, + ); + setNodeProms.push(setNodeProm); + } + await Promise.all(setNodeProms); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); await taskManager.startProcessing(); diff --git a/tests/nodes/NodeConnectionManager.timeout.test.ts b/tests/nodes/NodeConnectionManager.timeout.test.ts index f2b3dc3df..21286623e 100644 --- a/tests/nodes/NodeConnectionManager.timeout.test.ts +++ b/tests/nodes/NodeConnectionManager.timeout.test.ts @@ -6,18 +6,12 @@ import fs from 'fs'; import path from 'path'; import os from 'os'; import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; -import { QUICSocket } from '@matrixai/quic'; import { DB } from '@matrixai/db'; import NodeConnectionManager from '@/nodes/NodeConnectionManager'; import NodeConnection from '@/nodes/NodeConnection'; import * as keysUtils from '@/keys/utils'; import KeyRing from '@/keys/KeyRing'; -import ACL from '@/acl/ACL'; -import GestaltGraph from '@/gestalts/GestaltGraph'; import NodeGraph from '@/nodes/NodeGraph'; -import Sigchain from '@/sigchain/Sigchain'; -import TaskManager from '@/tasks/TaskManager'; -import NodeManager from '@/nodes/NodeManager'; import PolykeyAgent from '@/PolykeyAgent'; import { sleep } from '@/utils'; import { generateRandomNodeId } from './utils'; @@ -35,25 +29,18 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { ); const localHost = '127.0.0.1'; const password = 'password'; - const crypto = tlsTestUtils.createCrypto(); let dataDir: string; let remotePolykeyAgent1: PolykeyAgent; let remoteAddress1: NodeAddress; let remoteNodeId1: NodeId; - let clientSocket: QUICSocket; let keyRing: KeyRing; let db: DB; - let acl: ACL; - let gestaltGraph: GestaltGraph; let nodeGraph: NodeGraph; - let sigchain: Sigchain; - let taskManager: TaskManager; - let nodeManager: NodeManager; + let nodeConnectionManager: NodeConnectionManager; let tlsConfig: TLSConfig; - const handleStream = () => {}; beforeEach(async () => { dataDir = await fs.promises.mkdtemp( @@ -63,122 +50,79 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { // Setting up remote node const nodePathA = path.join(dataDir, 'agentA'); remotePolykeyAgent1 = await PolykeyAgent.createPolykeyAgent({ - nodePath: nodePathA, password, - networkConfig: { - agentHost: localHost, - }, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath: nodePathA, + agentServiceHost: localHost, + clientServiceHost: localHost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, logger: logger.getChild('AgentA'), }); remoteNodeId1 = remotePolykeyAgent1.keyRing.getNodeId(); remoteAddress1 = { - host: remotePolykeyAgent1.quicSocket.host as Host, - port: remotePolykeyAgent1.quicSocket.port as Port, + host: remotePolykeyAgent1.nodeConnectionManager.host, + port: remotePolykeyAgent1.nodeConnectionManager.port, }; - clientSocket = new QUICSocket({ - logger: logger.getChild('clientSocket'), - }); - await clientSocket.start({ - host: localHost, - }); - // Setting up client dependencies const keysPath = path.join(dataDir, 'keys'); keyRing = await KeyRing.createKeyRing({ password, keysPath, logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }); 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, - }); nodeGraph = await NodeGraph.createNodeGraph({ db, keyRing, logger, }); - sigchain = await Sigchain.createSigchain({ - db, - keyRing, - logger, - }); - taskManager = await TaskManager.createTaskManager({ - db, - logger, - }); tlsConfig = await tlsTestUtils.createTLSConfig(keyRing.keyPair); }); afterEach(async () => { - await taskManager.stopTasks(); - await sigchain.stop(); - await sigchain.destroy(); + await nodeConnectionManager?.stop(); await nodeGraph.stop(); await nodeGraph.destroy(); - await gestaltGraph.stop(); - await gestaltGraph.destroy(); - await acl.stop(); - await acl.destroy(); await db.stop(); await db.destroy(); await keyRing.stop(); await keyRing.destroy(); - await clientSocket.stop({ force: true }); - await taskManager.stop(); await remotePolykeyAgent1.stop(); }); - test('starting should add seed nodes to the node graph', async () => { - const nodeConnectionManager = new NodeConnectionManager({ + test('connection should timeout after connectionIdleTimeoutTime', async () => { + nodeConnectionManager = new NodeConnectionManager({ keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, - connectionTimeoutTime: 500, - }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, + options: { + connectionConnectTimeoutTime: 1000, + connectionIdleTimeoutTime: 100, + }, }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); - await taskManager.startProcessing(); await nodeGraph.setNode(remoteNodeId1, remoteAddress1); - // @ts-ignore: kidnap connections const connections = nodeConnectionManager.connections; // @ts-ignore: kidnap connections @@ -194,7 +138,7 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { expect(connAndLock?.connection).toBeDefined(); // Wait for timeout - await sleep(1000); + await sleep(300); const finalConnAndLock = connections.get( remoteNodeId1.toString() as NodeIdString, @@ -205,32 +149,19 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { await nodeConnectionManager.stop(); }); test('withConnection should extend timeout', async () => { - const nodeConnectionManager = new NodeConnectionManager({ + nodeConnectionManager = new NodeConnectionManager({ keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, - connectionTimeoutTime: 1000, - }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, + options: { + connectionIdleTimeoutTime: 1000, + }, }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); - await taskManager.startProcessing(); await nodeGraph.setNode(remoteNodeId1, remoteAddress1); @@ -275,32 +206,19 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { await nodeConnectionManager.stop(); }); test('withConnection should extend timeout', async () => { - const nodeConnectionManager = new NodeConnectionManager({ + nodeConnectionManager = new NodeConnectionManager({ keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, - connectionTimeoutTime: 1000, - }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, + options: { + connectionIdleTimeoutTime: 1000, + }, }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); - await taskManager.startProcessing(); await nodeGraph.setNode(remoteNodeId1, remoteAddress1); @@ -329,36 +247,23 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { await nodeConnectionManager.stop(); }); test('Connection can time out', async () => { - const nodeConnectionManager = new NodeConnectionManager({ + nodeConnectionManager = new NodeConnectionManager({ keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, - connectionTimeoutTime: 5000, - connectionConnectTime: 200, - }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, + options: { + connectionIdleTimeoutTime: 5000, + connectionConnectTimeoutTime: 200, + }, }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); - await taskManager.startProcessing(); const randomNodeId = generateRandomNodeId(); - await nodeManager.setNode(randomNodeId, { + await nodeGraph.setNode(randomNodeId, { host: '127.0.0.1' as Host, port: 12321 as Port, }); @@ -369,36 +274,23 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { ).rejects.toThrow(); }); test('Connection can time out with passed in timer', async () => { - const nodeConnectionManager = new NodeConnectionManager({ + nodeConnectionManager = new NodeConnectionManager({ keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, - connectionTimeoutTime: 5000, - connectionConnectTime: 200, - }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, + options: { + connectionIdleTimeoutTime: 5000, + connectionConnectTimeoutTime: 200, + }, }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); - await taskManager.startProcessing(); const randomNodeId = generateRandomNodeId(); - await nodeManager.setNode(randomNodeId, { + await nodeGraph.setNode(randomNodeId, { host: '127.0.0.1' as Host, port: 12321 as Port, }); @@ -415,36 +307,23 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { ).rejects.toThrow(); }); test('Connection can time out with passed in timer and signal', async () => { - const nodeConnectionManager = new NodeConnectionManager({ + nodeConnectionManager = new NodeConnectionManager({ keyRing, logger: logger.getChild(NodeConnectionManager.name), nodeGraph, tlsConfig, - crypto, - quicSocket: clientSocket, seedNodes: undefined, - connectionTimeoutTime: 5000, - connectionConnectTime: 200, - }); - nodeManager = new NodeManager({ - db, - gestaltGraph, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - logger, + options: { + connectionIdleTimeoutTime: 5000, + connectionConnectTimeoutTime: 200, + }, }); - await nodeManager.start(); await nodeConnectionManager.start({ - nodeManager, - handleStream, + host: localHost as Host, }); - await taskManager.startProcessing(); const randomNodeId = generateRandomNodeId(); - await nodeManager.setNode(randomNodeId, { + await nodeGraph.setNode(randomNodeId, { host: '127.0.0.1' as Host, port: 12321 as Port, }); diff --git a/tests/nodes/NodeGraph.test.ts b/tests/nodes/NodeGraph.test.ts index 01717a5f4..81e30705e 100644 --- a/tests/nodes/NodeGraph.test.ts +++ b/tests/nodes/NodeGraph.test.ts @@ -40,9 +40,11 @@ describe(`${NodeGraph.name} test`, () => { password, keysPath, logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }); dbKey = keysUtils.generateKey(); dbPath = `${dataDir}/db`; diff --git a/tests/nodes/NodeManager.test.ts b/tests/nodes/NodeManager.test.ts index 3538f9087..01918a48c 100644 --- a/tests/nodes/NodeManager.test.ts +++ b/tests/nodes/NodeManager.test.ts @@ -4,7 +4,6 @@ import type { Task } from '@/tasks/types'; import path from 'path'; import { DB } from '@matrixai/db'; import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; -import { QUICSocket } from '@matrixai/quic'; import { PromiseCancellable } from '@matrixai/async-cancellable'; import * as keysUtils from '@/keys/utils'; import NodeManager from '@/nodes/NodeManager'; @@ -41,7 +40,6 @@ describe(`${NodeManager.name} test`, () => { isSeedNode: mockedIsSeedNode, } as unknown as NodeConnectionManager; const dummySigchain = {} as Sigchain; - const crypto = tlsTestUtils.createCrypto(); let keyRing: KeyRing; let db: DB; @@ -51,11 +49,11 @@ describe(`${NodeManager.name} test`, () => { let sigchain: Sigchain; let taskManager: TaskManager; - let clientSocket: QUICSocket; let tlsConfig: TLSConfig; + let nodeConnectionManager: NodeConnectionManager; + let nodeManager: NodeManager; let server: PolykeyAgent; - let nodeConnectionManager; beforeEach(async () => { // Setting up client dependencies @@ -64,9 +62,11 @@ describe(`${NodeManager.name} test`, () => { password, keysPath, logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }); const dbPath = path.join(dataDir, 'db'); db = await DB.createDB({ @@ -97,18 +97,12 @@ describe(`${NodeManager.name} test`, () => { logger, }); - clientSocket = new QUICSocket({ - logger: logger.getChild('clientSocket'), - }); - await clientSocket.start({ - host: localHost, - }); - tlsConfig = await tlsTestUtils.createTLSConfig(keyRing.keyPair); }); afterEach(async () => { await taskManager.stop(); + await nodeManager?.stop(); await nodeConnectionManager?.stop(); await sigchain.stop(); await sigchain.destroy(); @@ -124,7 +118,6 @@ describe(`${NodeManager.name} test`, () => { await keyRing.stop(); await keyRing.destroy(); - await clientSocket.stop({ force: true }); await server?.stop(); }); @@ -334,8 +327,6 @@ describe(`${NodeManager.name} test`, () => { keyRing, nodeGraph, tlsConfig, - crypto, - quicSocket: clientSocket, logger, }); const nodeManager = new NodeManager({ @@ -350,29 +341,32 @@ describe(`${NodeManager.name} test`, () => { }); await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager }); + await nodeConnectionManager.start({ + host: localHost as Host, + }); server = await PolykeyAgent.createPolykeyAgent({ password: 'password', - nodePath: path.join(dataDir, 'server'), - networkConfig: { - agentHost: '127.0.0.1', + options: { + nodePath: path.join(dataDir, 'server'), + agentServiceHost: localHost, + clientServiceHost: localHost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, logger: logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }, }); const serverNodeId = server.keyRing.getNodeId(); const serverNodeAddress: NodeAddress = { - host: server.quicSocket.host as Host, - port: server.quicSocket.port as Port, + host: server.nodeConnectionManager.host as Host, + port: server.nodeConnectionManager.port as Port, }; await nodeGraph.setNode(serverNodeId, serverNodeAddress); - const expectedHost = clientSocket.host; - const expectedPort = clientSocket.port; + const expectedHost = nodeConnectionManager.host; + const expectedPort = nodeConnectionManager.port; const expectedNodeId = keyRing.getNodeId(); const nodeData = await server.nodeGraph.getNode(expectedNodeId); @@ -556,8 +550,6 @@ describe(`${NodeManager.name} test`, () => { keyRing, nodeGraph, tlsConfig, - crypto, - quicSocket: clientSocket, logger, }); const nodeManager = new NodeManager({ @@ -570,7 +562,9 @@ describe(`${NodeManager.name} test`, () => { taskManager, logger, }); - await nodeConnectionManager.start({ nodeManager }); + await nodeConnectionManager.start({ + host: localHost as Host, + }); await nodeManager.start(); await expect(nodeManager.refreshBucket(100)).resolves.not.toThrow(); diff --git a/tests/nodes/utils.test.ts b/tests/nodes/utils.test.ts index 21cb7108a..4d9d47df9 100644 --- a/tests/nodes/utils.test.ts +++ b/tests/nodes/utils.test.ts @@ -10,6 +10,7 @@ import { DB } from '@matrixai/db'; import * as nodesUtils from '@/nodes/utils'; import * as keysUtils from '@/keys/utils'; import * as utils from '@/utils'; +import * as rpcErrors from '@/rpc/errors'; import * as testNodesUtils from './utils'; describe('nodes/utils', () => { @@ -200,4 +201,20 @@ describe('nodes/utils', () => { expect(nodesUtils.bucketIndex(baseNodeId, randomDistance)).toEqual(i); } }); + test('code and reason converters', async () => { + function check(reason: any): any { + const _reason = new reason(); + const code = nodesUtils.reasonToCode('read', _reason); + const convertedReason = nodesUtils.codeToReason('read', code); + expect(convertedReason).toBeInstanceOf(reason); + } + + check(rpcErrors.ErrorRPCHandlerFailed); + check(rpcErrors.ErrorRPCMessageLength); + check(rpcErrors.ErrorRPCMissingResponse); + check(rpcErrors.ErrorRPCOutputStreamError); + check(rpcErrors.ErrorPolykeyRemote); + check(rpcErrors.ErrorRPCStreamEnded); + check(rpcErrors.ErrorRPCTimedOut); + }); }); diff --git a/tests/nodes/utils.ts b/tests/nodes/utils.ts index 63ace36b7..960414a74 100644 --- a/tests/nodes/utils.ts +++ b/tests/nodes/utils.ts @@ -71,13 +71,13 @@ function generateNodeIdForBucket( async function nodesConnect(localNode: PolykeyAgent, remoteNode: PolykeyAgent) { // Add remote node's details to local node await localNode.nodeManager.setNode(remoteNode.keyRing.getNodeId(), { - host: remoteNode.quicSocket.host, - port: remoteNode.quicSocket.port, + host: remoteNode.nodeConnectionManager.host, + port: remoteNode.nodeConnectionManager.port, } as NodeAddress); // Add local node's details to remote node await remoteNode.nodeManager.setNode(localNode.keyRing.getNodeId(), { - host: localNode.quicSocket.host, - port: localNode.quicSocket.port, + host: localNode.nodeConnectionManager.host, + port: localNode.nodeConnectionManager.port, } as NodeAddress); } @@ -141,6 +141,35 @@ async function verify(key: ArrayBuffer, data: ArrayBuffer, sig: ArrayBuffer) { return webcrypto.subtle.verify('HMAC', cryptoKey, sig, data); } +/** + * This will create a `reasonToCode` and `codeToReason` functions that will + * allow errors to "jump" the network boundary. It does this by mapping the + * errors to an incrementing code and returning them on the other end of the + * connection. + * + * Note: this should ONLY be used for testing as it requires the client and + * server to share the same instance of `reasonToCode` and `codeToReason`. + */ +function createReasonConverters() { + const reasonMap = new Map(); + let code = 0; + + const reasonToCode = (_type, reason) => { + code++; + reasonMap.set(code, reason); + return code; + }; + + const codeToReason = (_type, code) => { + return reasonMap.get(code) ?? new Error('Reason not found'); + }; + + return { + reasonToCode, + codeToReason, + }; +} + export { generateRandomNodeId, generateNodeIdForBucket, @@ -150,4 +179,5 @@ export { uniqueNodeIdArb, sign, verify, + createReasonConverters, }; diff --git a/tests/notifications/NotificationsManager.test.ts b/tests/notifications/NotificationsManager.test.ts index 98d82b9e1..8e12c2551 100644 --- a/tests/notifications/NotificationsManager.test.ts +++ b/tests/notifications/NotificationsManager.test.ts @@ -4,14 +4,12 @@ import type { VaultActions, VaultName } from '@/vaults/types'; import type { Notification, NotificationData } from '@/notifications/types'; import type { Key } from '@/keys/types'; import type GestaltGraph from '@/gestalts/GestaltGraph'; -import type { Host as QUICHost } from '@matrixai/quic/dist/types'; import fs from 'fs'; import os from 'os'; import path from 'path'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { IdInternal } from '@matrixai/id'; -import { QUICSocket } from '@matrixai/quic'; import TaskManager from '@/tasks/TaskManager'; import PolykeyAgent from '@/PolykeyAgent'; import ACL from '@/acl/ACL'; @@ -31,6 +29,7 @@ import * as tlsTestsUtils from '../utils/tls'; describe('NotificationsManager', () => { const password = 'password'; + const localhost = '127.0.0.1'; const logger = new Logger( `${NotificationsManager.name} Test`, LogLevel.WARN, @@ -53,7 +52,6 @@ describe('NotificationsManager', () => { let taskManager: TaskManager; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; - let quicSocket: QUICSocket; let keyRing: KeyRing; let sigchain: Sigchain; @@ -112,20 +110,11 @@ describe('NotificationsManager', () => { logger, lazy: true, }); - const crypto = tlsTestsUtils.createCrypto(); - quicSocket = new QUICSocket({ - logger, - }); - await quicSocket.start({ - host: '127.0.0.1' as QUICHost, - }); const tlsConfig = await tlsTestsUtils.createTLSConfig(keyRing.keyPair); nodeConnectionManager = new NodeConnectionManager({ nodeGraph, keyRing, tlsConfig, - crypto, - quicSocket, logger, }); nodeManager = new NodeManager({ @@ -139,26 +128,26 @@ describe('NotificationsManager', () => { logger, }); await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager, handleStream: () => {} }); + await nodeConnectionManager.start({ host: localhost as Host }); await taskManager.start(); // Set up node for receiving notifications receiver = await PolykeyAgent.createPolykeyAgent({ password: password, - nodePath: path.join(dataDir, 'receiver'), - networkConfig: { - agentHost: '127.0.0.1', - clientHost: '127.0.0.1', + options: { + nodePath: path.join(dataDir, 'receiver'), + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }, }); await nodeGraph.setNode(receiver.keyRing.getNodeId(), { - host: receiver.quicSocket.host as Host, - port: receiver.quicSocket.port as Port, + host: receiver.nodeConnectionManager.host as Host, + port: receiver.nodeConnectionManager.port as Port, }); }, globalThis.defaultTimeout); afterEach(async () => { @@ -166,7 +155,6 @@ describe('NotificationsManager', () => { await taskManager.stopTasks(); await receiver.stop(); await nodeConnectionManager.stop(); - await quicSocket.stop(); await nodeManager.stop(); await nodeGraph.stop(); await sigchain.stop(); @@ -791,9 +779,8 @@ describe('NotificationsManager', () => { vaults: {}, }); await notificationsManager.receiveNotification(notification); - const receivedInvite = await notificationsManager.findGestaltInvite( - senderId, - ); + const receivedInvite = + await notificationsManager.findGestaltInvite(senderId); expect(receivedInvite).toEqual(notification); // Reverse side-effects await notificationsManager.clearNotifications(); diff --git a/tests/status/Status.test.ts b/tests/status/Status.test.ts index 494d63825..0ed5b22be 100644 --- a/tests/status/Status.test.ts +++ b/tests/status/Status.test.ts @@ -26,8 +26,8 @@ describe('Status', () => { }); test('status readiness', async () => { const status = new Status({ - statusPath: path.join(dataDir, config.defaults.statusBase), - statusLockPath: path.join(dataDir, config.defaults.statusLockBase), + statusPath: path.join(dataDir, config.paths.statusBase), + statusLockPath: path.join(dataDir, config.paths.statusLockBase), fs: fs, logger: logger, }); @@ -48,8 +48,8 @@ describe('Status', () => { }); test('status transitions', async () => { const status = new Status({ - statusPath: path.join(dataDir, config.defaults.statusBase), - statusLockPath: path.join(dataDir, config.defaults.statusLockBase), + statusPath: path.join(dataDir, config.paths.statusBase), + statusLockPath: path.join(dataDir, config.paths.statusLockBase), fs: fs, logger: logger, }); @@ -86,16 +86,16 @@ describe('Status', () => { }); test('start with existing statusPath or statusLockPath', async () => { await fs.promises.writeFile( - path.join(dataDir, config.defaults.statusBase), + path.join(dataDir, config.paths.statusBase), 'hello world', ); await fs.promises.writeFile( - path.join(dataDir, config.defaults.statusLockBase), + path.join(dataDir, config.paths.statusLockBase), 'hello world', ); const status = new Status({ - statusPath: path.join(dataDir, config.defaults.statusBase), - statusLockPath: path.join(dataDir, config.defaults.statusLockBase), + statusPath: path.join(dataDir, config.paths.statusBase), + statusLockPath: path.join(dataDir, config.paths.statusLockBase), fs: fs, logger: logger, }); @@ -108,8 +108,8 @@ describe('Status', () => { }); test('readStatus on non-existent status', async () => { const status = new Status({ - statusPath: path.join(dataDir, config.defaults.statusBase), - statusLockPath: path.join(dataDir, config.defaults.statusLockBase), + statusPath: path.join(dataDir, config.paths.statusBase), + statusLockPath: path.join(dataDir, config.paths.statusLockBase), fs: fs, logger: logger, }); @@ -117,8 +117,8 @@ describe('Status', () => { }); test('updating live status', async () => { const status = new Status({ - statusPath: path.join(dataDir, config.defaults.statusBase), - statusLockPath: path.join(dataDir, config.defaults.statusLockBase), + statusPath: path.join(dataDir, config.paths.statusBase), + statusLockPath: path.join(dataDir, config.paths.statusLockBase), fs: fs, logger: logger, }); @@ -159,14 +159,14 @@ describe('Status', () => { }); test('singleton running status', async () => { const status1 = new Status({ - statusPath: path.join(dataDir, config.defaults.statusBase), - statusLockPath: path.join(dataDir, config.defaults.statusLockBase), + statusPath: path.join(dataDir, config.paths.statusBase), + statusLockPath: path.join(dataDir, config.paths.statusLockBase), fs: fs, logger: logger, }); const status2 = new Status({ - statusPath: path.join(dataDir, config.defaults.statusBase), - statusLockPath: path.join(dataDir, config.defaults.statusLockBase), + statusPath: path.join(dataDir, config.paths.statusBase), + statusLockPath: path.join(dataDir, config.paths.statusLockBase), fs: fs, logger: logger, }); @@ -182,8 +182,8 @@ describe('Status', () => { }); test('wait for transitions', async () => { const status = new Status({ - statusPath: path.join(dataDir, config.defaults.statusBase), - statusLockPath: path.join(dataDir, config.defaults.statusLockBase), + statusPath: path.join(dataDir, config.paths.statusBase), + statusLockPath: path.join(dataDir, config.paths.statusLockBase), fs: fs, logger: logger, }); @@ -213,8 +213,8 @@ describe('Status', () => { }); test('parse error when statusPath is corrupted', async () => { const status = new Status({ - statusPath: path.join(dataDir, config.defaults.statusBase), - statusLockPath: path.join(dataDir, config.defaults.statusLockBase), + statusPath: path.join(dataDir, config.paths.statusBase), + statusLockPath: path.join(dataDir, config.paths.statusLockBase), fs: fs, logger: logger, }); @@ -227,8 +227,8 @@ describe('Status', () => { }); test('status transitions are serialised', async () => { const status = new Status({ - statusPath: path.join(dataDir, config.defaults.statusBase), - statusLockPath: path.join(dataDir, config.defaults.statusLockBase), + statusPath: path.join(dataDir, config.paths.statusBase), + statusLockPath: path.join(dataDir, config.paths.statusLockBase), fs: fs, logger: logger, }); @@ -283,8 +283,8 @@ describe('Status', () => { }); test('wait for has at-least-once semantics', async () => { const status = new Status({ - statusPath: path.join(dataDir, config.defaults.statusBase), - statusLockPath: path.join(dataDir, config.defaults.statusLockBase), + statusPath: path.join(dataDir, config.paths.statusBase), + statusLockPath: path.join(dataDir, config.paths.statusLockBase), fs: fs, logger: logger, }); diff --git a/tests/utils/utils.ts b/tests/utils/utils.ts index 7d9d95c4b..f323ae90c 100644 --- a/tests/utils/utils.ts +++ b/tests/utils/utils.ts @@ -80,10 +80,46 @@ function trackTimers() { return timerMap; } +/** + * Utility for creating a promise that resolves or rejects based on events from a target. + */ +function promFromEvent< + EResolve extends Event = Event, + EReject extends Event = Event, + T extends EventTarget = EventTarget, +>( + target: T, + resolveEvent: new () => EResolve, + rejectEvent?: new () => EReject, +) { + const handleResolveEvent = (evt: EResolve) => prom.resolveP(evt); + const handleRejectEvent = (evt: EReject) => prom.rejectP(evt); + const prom = promise(); + target.addEventListener(resolveEvent.name, handleResolveEvent); + if (rejectEvent != null) { + target.addEventListener(rejectEvent.name, handleRejectEvent); + } + // Prevent unhandled rejection errors + void prom.p + .then( + () => {}, + () => {}, + ) + .finally(() => { + // Clean up + target.removeEventListener(resolveEvent.name, handleResolveEvent); + if (rejectEvent != null) { + target.removeEventListener(rejectEvent.name, handleRejectEvent); + } + }); + return prom; +} + export { generateRandomNodeId, expectRemoteError, testIf, describeIf, trackTimers, + promFromEvent, }; diff --git a/tests/vaults/VaultManager.test.ts b/tests/vaults/VaultManager.test.ts index 5b597db43..f4a5ddb17 100644 --- a/tests/vaults/VaultManager.test.ts +++ b/tests/vaults/VaultManager.test.ts @@ -7,7 +7,6 @@ import type { } from '@/vaults/types'; import type NotificationsManager from '@/notifications/NotificationsManager'; import type { Host, Port } from '@/network/types'; -import type NodeManager from '@/nodes/NodeManager'; import fs from 'fs'; import os from 'os'; import path from 'path'; @@ -17,7 +16,6 @@ import { DB } from '@matrixai/db'; import { destroyed, running } from '@matrixai/async-init'; import git from 'isomorphic-git'; import { RWLockWriter } from '@matrixai/async-locks'; -import { QUICSocket } from '@matrixai/quic'; import TaskManager from '@/tasks/TaskManager'; import ACL from '@/acl/ACL'; import GestaltGraph from '@/gestalts/GestaltGraph'; @@ -36,7 +34,7 @@ import * as testUtils from '../utils'; import * as tlsTestsUtils from '../utils/tls'; describe('VaultManager', () => { - const localHost = '127.0.0.1'; + const localhost = '127.0.0.1'; const logger = new Logger('VaultManager Test', LogLevel.WARN, [ new StreamHandler(), ]); @@ -474,7 +472,6 @@ describe('VaultManager', () => { let keyRing: KeyRing; let nodeGraph: NodeGraph; let nodeConnectionManager: NodeConnectionManager; - let quicSocket: QUICSocket; let remoteKeynode1: PolykeyAgent, remoteKeynode2: PolykeyAgent; let localNodeId: NodeId; let taskManager: TaskManager; @@ -487,41 +484,43 @@ describe('VaultManager', () => { remoteKeynode1 = await PolykeyAgent.createPolykeyAgent({ password, - logger: logger.getChild('Remote Keynode 1'), - nodePath: path.join(allDataDir, 'remoteKeynode1'), - networkConfig: { - agentHost: localHost, - }, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath: path.join(allDataDir, 'remoteKeynode1'), + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, + logger: logger.getChild('Remote Keynode 1'), }); remoteKeynode1Id = remoteKeynode1.keyRing.getNodeId(); remoteKeynode2 = await PolykeyAgent.createPolykeyAgent({ password, - logger: logger.getChild('Remote Keynode 2'), - nodePath: path.join(allDataDir, 'remoteKeynode2'), - networkConfig: { - agentHost: localHost, - }, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, + options: { + nodePath: path.join(allDataDir, 'remoteKeynode2'), + agentServiceHost: localhost, + clientServiceHost: localhost, + keys: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, }, + logger: logger.getChild('Remote Keynode 2'), }); remoteKeynode2Id = remoteKeynode2.keyRing.getNodeId(); // Adding details to each agent await remoteKeynode1.nodeGraph.setNode(remoteKeynode2Id, { - host: remoteKeynode2.quicSocket.host as Host, - port: remoteKeynode2.quicSocket.port as Port, + host: remoteKeynode2.nodeConnectionManager.host as Host, + port: remoteKeynode2.nodeConnectionManager.port as Port, }); await remoteKeynode2.nodeGraph.setNode(remoteKeynode1Id, { - host: remoteKeynode1.quicSocket.host as Host, - port: remoteKeynode1.quicSocket.port as Port, + host: remoteKeynode1.nodeConnectionManager.host as Host, + port: remoteKeynode1.nodeConnectionManager.port as Port, }); await remoteKeynode1.gestaltGraph.setNode({ @@ -568,34 +567,22 @@ describe('VaultManager', () => { lazy: true, logger, }); - const crypto = tlsTestsUtils.createCrypto(); - quicSocket = new QUICSocket({ - logger, - }); - await quicSocket.start({ - host: '127.0.0.1', - }); const tlsConfig = await tlsTestsUtils.createTLSConfig(keyRing.keyPair); nodeConnectionManager = new NodeConnectionManager({ keyRing, nodeGraph, tlsConfig, - crypto, - quicSocket, logger, }); - await nodeConnectionManager.start({ - nodeManager: { setNode: jest.fn() } as unknown as NodeManager, - handleStream: () => {}, - }); + await nodeConnectionManager.start({ host: localhost as Host }); await taskManager.startProcessing(); await nodeGraph.setNode(remoteKeynode1Id, { - host: remoteKeynode1.quicSocket.host as Host, - port: remoteKeynode1.quicSocket.port as Port, + host: remoteKeynode1.nodeConnectionManager.host as Host, + port: remoteKeynode1.nodeConnectionManager.port as Port, }); await nodeGraph.setNode(remoteKeynode2Id, { - host: remoteKeynode2.quicSocket.host as Host, - port: remoteKeynode2.quicSocket.port as Port, + host: remoteKeynode2.nodeConnectionManager.host as Host, + port: remoteKeynode2.nodeConnectionManager.port as Port, }); }); afterEach(async () => { @@ -603,7 +590,6 @@ describe('VaultManager', () => { await taskManager.stopTasks(); await remoteKeynode1.vaultManager.destroyVault(remoteVaultId); await nodeConnectionManager.stop(); - await quicSocket.stop(); await nodeGraph.stop(); await nodeGraph.destroy(); await keyRing.stop(); @@ -1396,8 +1382,8 @@ describe('VaultManager', () => { // Letting nodeGraph know where the remote agent is await nodeGraph.setNode(targetNodeId, { - host: remoteKeynode1.quicSocket.host as Host, - port: remoteKeynode1.quicSocket.port as Port, + host: remoteKeynode1.nodeConnectionManager.host as Host, + port: remoteKeynode1.nodeConnectionManager.port as Port, }); await remoteKeynode1.gestaltGraph.setNode({ diff --git a/tsconfig.json b/tsconfig.json index a12043658..3d794c9aa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,7 @@ "esModuleInterop": true, "allowSyntheticDefaultImports": true, "resolveJsonModule": true, + "skipLibCheck": true, "moduleResolution": "node", "module": "CommonJS", "target": "ES2022",