diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 774f0301..00000000 --- a/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules -dist -.test-reports -.coverage -.nostr \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 8a7ae4fe..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,53 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - parserOptions: { - sourceType: 'module', - }, - plugins: ['@typescript-eslint'], - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], - root: true, - env: { - node: true, - }, - ignorePatterns: ['dist', 'tslint.json', 'node_modules'], - rules: { - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-unused-vars': [ - 'error', - { - argsIgnorePattern: '^_', - caughtErrors: 'none', - }, - ], - '@typescript-eslint/no-require-imports': 'off', - - semi: ['error', 'never'], - quotes: ['error', 'single', { avoidEscape: true }], - 'sort-imports': [ - 'error', - { - ignoreCase: true, - allowSeparatedGroups: true, - }, - ], - curly: [2, 'multi-line'], - 'max-len': [ - 'error', - { - code: 120, - ignoreStrings: true, - ignoreTemplateLiterals: true, - ignoreRegExpLiterals: true, - }, - ], - 'comma-dangle': ['error', 'always-multiline'], - }, - overrides: [ - { - files: ['test/**/*.ts', '**/*.spec.ts'], - rules: { - '@typescript-eslint/no-unused-expressions': 'off', - }, - }, - ], -} \ No newline at end of file diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 5d752368..7bc5bf8e 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -40,7 +40,7 @@ jobs: cache: npm - name: Install package dependencies run: npm ci - - name: Run ESLint + - name: Run Biome run: npm run lint - name: Run Knip run: npm run knip @@ -56,7 +56,7 @@ jobs: cache: npm - name: Install package dependencies run: npm ci - - name: Run ESLint + - name: Run build check run: npm run build:check test-units-and-cover: name: Unit Tests And Coverage diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c691fa83..4da92ab3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ Run dead code and dependency analysis before opening a pull request: npm run knip ``` -`npm run lint` now runs Knip first, then ESLint. +`npm run lint` now runs Biome. ## Pull Request Process @@ -24,3 +24,12 @@ npm run knip Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 3. You may merge the Pull Request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you. + +## Code Quality + +Run Biome checks before opening a pull request: + +``` +npm run lint +npm run format:check +``` diff --git a/README.md b/README.md index 4b391489..a85e10f4 100644 --- a/README.md +++ b/README.md @@ -470,6 +470,17 @@ Start: ## Tests +### Linting and formatting (Biome) + +Run code quality checks with Biome: + + ``` + npm run lint + npm run lint:fix + npm run format + npm run format:check + ``` + ### Unit tests Open a terminal and change to the project's directory: diff --git a/biome.json b/biome.json new file mode 100644 index 00000000..473e5d60 --- /dev/null +++ b/biome.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.11/schema.json", + "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, + "files": { + "ignoreUnknown": false, + "includes": [ + "**", + "!**/node_modules/**", + "!**/dist/**", + "!**/.test-reports/**", + "!**/.coverage/**", + "!**/.nostr/**", + "!**/tslint.json" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "lineWidth": 120 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": false, + "correctness": { "noUnusedVariables": "error" }, + "style": { "useBlockStatements": "warn" }, + "suspicious": { "noExplicitAny": "off" } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "semicolons": "asNeeded", + "trailingCommas": "all" + } + } +} diff --git a/package-lock.json b/package-lock.json index a0f84bd9..708e41a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,12 +28,11 @@ "zod": "^3.22.4" }, "devDependencies": { + "@biomejs/biome": "^2.4.11", "@commitlint/cli": "17.2.0", "@commitlint/config-conventional": "17.2.0", "@cucumber/cucumber": "10.2.1", "@cucumber/pretty-formatter": "1.0.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "^9.39.1", "@semantic-release/commit-analyzer": "9.0.2", "@semantic-release/git": "10.0.1", "@semantic-release/github": "8.1.0", @@ -45,20 +44,16 @@ "@types/express": "4.17.21", "@types/js-yaml": "4.0.5", "@types/mocha": "^9.1.1", - "@types/node": "^24.0.0", + "@types/node": "^24.12.2", "@types/pg": "^8.6.5", "@types/ramda": "^0.28.13", "@types/sinon": "^10.0.11", "@types/sinon-chai": "^3.2.8", "@types/ws": "^8.5.12", - "@typescript-eslint/eslint-plugin": "^8.58.1", - "@typescript-eslint/parser": "^8.58.1", "chai": "^4.3.6", "chai-as-promised": "^7.1.1", "conventional-changelog-conventionalcommits": "5.0.0", - "cross-env": "^10.1.0", "cz-conventional-changelog": "3.3.0", - "eslint": "^9.39.4", "husky": "8.0.2", "knip": "2.43.0", "mocha": "^11.7.5", @@ -147,7 +142,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -401,6 +395,169 @@ "node": ">=6.9.0" } }, + "node_modules/@biomejs/biome": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.12.tgz", + "integrity": "sha512-Rro7adQl3NLq/zJCIL98eElXKI8eEiBtoeu5TbXF/U3qbjuSc7Jb5rjUbeHHcquDWeSf3HnGP7XI5qGrlRk/pA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.4.12", + "@biomejs/cli-darwin-x64": "2.4.12", + "@biomejs/cli-linux-arm64": "2.4.12", + "@biomejs/cli-linux-arm64-musl": "2.4.12", + "@biomejs/cli-linux-x64": "2.4.12", + "@biomejs/cli-linux-x64-musl": "2.4.12", + "@biomejs/cli-win32-arm64": "2.4.12", + "@biomejs/cli-win32-x64": "2.4.12" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.12.tgz", + "integrity": "sha512-BnMU4Pc3ciEVteVpZ0BK33MLr7X57F5w1dwDLDn+/iy/yTrA4Q/N2yftidFtsA4vrDh0FMXDpacNV/Tl3fbmng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.12.tgz", + "integrity": "sha512-x9uJ0bI1rJsWICp3VH8w/5PnAVD3A7SqzDpbrfoUQX1QyWrK5jSU4fRLo/wSgGeplCivbxBRKmt5Xq4/nWvq8A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.12.tgz", + "integrity": "sha512-tOwuCuZZtKi1jVzbk/5nXmIsziOB6yqN8c9r9QM0EJYPU6DpQWf11uBOSCfFKKM4H3d9ZoarvlgMfbcuD051Pw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.12.tgz", + "integrity": "sha512-FhfpkAAlKL6kwvcVap0Hgp4AhZmtd3YImg0kK1jd7C/aSoh4SfsB2f++yG1rU0lr8Y5MCFJrcSkmssiL9Xnnig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.12.tgz", + "integrity": "sha512-8pFeAnLU9QdW9jCIslB/v82bI0lhBmz2ZAKc8pVMFPO0t0wAHsoEkrUQUbMkIorTRIjbqyNZHA3lEXavsPWYSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.12.tgz", + "integrity": "sha512-dwTIgZrGutzhkQCuvHynCkyW6hJxUuyZqKKO0YNfaS2GUoRO+tOvxXZqZB6SkWAOdfZTzwaw8IEdUnIkHKHoew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.12.tgz", + "integrity": "sha512-B0DLnx0vA9ya/3v7XyCaP+/lCpnbWbMOfUFFve+xb5OxyYvdHaS55YsSddr228Y+JAFk58agCuZTsqNiw2a6ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.12.tgz", + "integrity": "sha512-yMckRzTyZ83hkk8iDFWswqSdU8tvZxspJKnYNh7JZr/zhZNOlzH13k4ecboU6MurKExCe2HUkH75pGI/O2JwGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -999,7 +1156,6 @@ "integrity": "sha512-Kxap9uP5jD8tHUZVjTWgzxemi/0uOsbGjd4LBOSxcJoOCRbESFwemUzilJuzNTB8pcTQUh8D5oudUyxfkJOKmA==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@cucumber/messages": ">=17.1.1" } @@ -1040,13 +1196,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@epic-web/invariant": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", - "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", - "dev": true, - "license": "MIT" - }, "node_modules/@ericcornelissen/bash-parser": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@ericcornelissen/bash-parser/-/bash-parser-0.5.3.tgz", @@ -1077,275 +1226,6 @@ "node": ">=4" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.5" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", - "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/types": "^0.15.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", - "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.2", - "@humanfs/types": "^0.15.0", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/types": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", - "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1698,7 +1578,6 @@ "integrity": "sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^3.0.0", "@octokit/graphql": "^5.0.0", @@ -1975,7 +1854,6 @@ "integrity": "sha512-YfcB2QrX+Wx1o6LD1G2Y2fhDhOix/bAY/oAnMpHoNLsKkWIRbt1oKLkIFvxBMzLwAEPqnYWguJrYC+J6i4ywbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "bole": "^5.0.0", "ndjson": "^2.0.0" @@ -2175,7 +2053,6 @@ "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.4.2.tgz", "integrity": "sha512-oUdEjE0I7JS5AyaAjkD3aOXn9NhO7XKyPyXEyrgFDu++VrVBHUPnV6dgEya9TcMuj5nIJRuCzCm8ZP+c9zCHPw==", "license": "MIT", - "peer": true, "dependencies": { "cluster-key-slot": "1.1.1", "generic-pool": "3.9.0", @@ -3011,13 +2888,6 @@ "@types/ms": "*" } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", @@ -3058,13 +2928,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -3102,7 +2965,6 @@ "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -3237,402 +3099,6 @@ "@types/node": "*" } }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz", - "integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.58.2", - "@typescript-eslint/type-utils": "8.58.2", - "@typescript-eslint/utils": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.58.2", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz", - "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.58.2", - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz", - "integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.58.2", - "@typescript-eslint/types": "^8.58.2", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/project-service/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/project-service/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz", - "integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz", - "integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz", - "integrity": "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2", - "@typescript-eslint/utils": "8.58.2", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/types": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz", - "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz", - "integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.58.2", - "@typescript-eslint/tsconfig-utils": "8.58.2", - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", - "integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.58.2", - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz", - "integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.2", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@zkochan/retry": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@zkochan/retry/-/retry-0.2.0.tgz", @@ -3675,7 +3141,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3683,16 +3148,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, "node_modules/acorn-walk": { "version": "8.3.5", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", @@ -4326,7 +3781,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -4556,7 +4010,6 @@ "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -5302,7 +4755,6 @@ "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", @@ -5402,24 +4854,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cross-env": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", - "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@epic-web/invariant": "^1.0.0", - "cross-spawn": "^7.0.6" - }, - "bin": { - "cross-env": "dist/bin/cross-env.js", - "cross-env-shell": "dist/bin/cross-env-shell.js" - }, - "engines": { - "node": ">=20" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -5658,13 +5092,6 @@ "node": ">=4.0.0" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, "node_modules/default-require-extensions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", @@ -6124,230 +5551,78 @@ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">= 0.4" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">= 0.4" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "es-errors": "^1.3.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">= 0.4" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=6" } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/esm": { @@ -6359,37 +5634,6 @@ "node": ">=6" } }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -6404,52 +5648,6 @@ "node": ">=4" } }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -6646,20 +5844,6 @@ "node": ">= 6" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", @@ -6735,19 +5919,6 @@ "node": ">=0.8.0" } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -6913,27 +6084,6 @@ "flat": "cli.js" } }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, "node_modules/follow-redirects": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", @@ -7340,19 +6490,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/global-dirs": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", @@ -7421,19 +6558,6 @@ "which": "bin/which" } }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -8534,7 +7658,6 @@ "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -8571,13 +7694,6 @@ "node": ">=6" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -8599,13 +7715,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -8680,16 +7789,6 @@ "dev": true, "license": "MIT" }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -8907,20 +8006,6 @@ "node": ">= 0.6.3" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -9326,7 +8411,6 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -9952,13 +9036,6 @@ "thenify-all": "^1.0.0" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, "node_modules/ndjson": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ndjson/-/ndjson-2.0.0.tgz", @@ -12024,7 +11101,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -12470,24 +11546,6 @@ "opener": "bin/opener-bin.js" } }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -12998,7 +12056,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.9.0.tgz", "integrity": "sha512-ZJM+qkEbtOHRuXjmvBtOgNOXOtLSbxiMiUVMgE4rV6Zwocy03RicCVvDXgx8l4Biwo8/qORUnEqn2fdQzV7KCg==", "license": "MIT", - "peer": true, "dependencies": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", @@ -13370,16 +12427,6 @@ "node": ">=0.10.0" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/pretty-ms": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-8.0.0.tgz", @@ -13481,16 +12528,6 @@ "node": ">=10" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -14040,7 +13077,8 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/regexp-match-indices": { "version": "1.0.2", @@ -14448,7 +13486,6 @@ "integrity": "sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", @@ -14554,7 +13591,6 @@ "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -16753,7 +15789,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -16894,19 +15929,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, "node_modules/ts-dedent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", @@ -16923,7 +15945,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -17154,19 +16175,6 @@ "node": ">=0.6.11 <=0.7.0 || >=0.7.3" } }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/type-detect": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", @@ -17219,7 +16227,6 @@ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -17415,16 +16422,6 @@ "tslib": "^2.0.3" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/url-join": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", @@ -18056,7 +17053,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 72e62788..45b1804f 100644 --- a/package.json +++ b/package.json @@ -31,11 +31,13 @@ "prestart": "npm run build", "start": "cd dist && node src/index.js", "build:check": "npm run build -- --noEmit", - "lint": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint --ext .ts ./src ./test", - "lint:report": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint -o .lint-reports/eslint.json -f json --ext .ts ./src ./test", - "lint:fix": "npm run lint -- --fix", - "knip": "knip --config .knip.json --production --no-progress --reporter compact", + "knip": "knip --config .knip.json --production --include files,dependencies --no-progress --reporter compact", + "lint": "biome lint ./src ./test", "check:all": "npm run lint && npm run knip", + "lint:report": "mkdir -p .lint-reports && biome lint --reporter=json --reporter-file=.lint-reports/biome.json ./src ./test", + "lint:fix": "npm run lint -- --write", + "format": "biome format --write ./src ./test", + "format:check": "biome format ./src ./test", "import": "node -r ts-node/register src/import-events.ts", "db:migrate": "knex migrate:latest", "db:migrate:rollback": "knex migrate:rollback", @@ -77,12 +79,11 @@ }, "homepage": "https://github.com/cameri/nostream#readme", "devDependencies": { + "@biomejs/biome": "^2.4.11", "@commitlint/cli": "17.2.0", "@commitlint/config-conventional": "17.2.0", "@cucumber/cucumber": "10.2.1", "@cucumber/pretty-formatter": "1.0.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "^9.39.1", "@semantic-release/commit-analyzer": "9.0.2", "@semantic-release/git": "10.0.1", "@semantic-release/github": "8.1.0", @@ -94,20 +95,16 @@ "@types/express": "4.17.21", "@types/js-yaml": "4.0.5", "@types/mocha": "^9.1.1", - "@types/node": "^24.0.0", + "@types/node": "^24.12.2", "@types/pg": "^8.6.5", "@types/ramda": "^0.28.13", "@types/sinon": "^10.0.11", "@types/sinon-chai": "^3.2.8", "@types/ws": "^8.5.12", - "@typescript-eslint/eslint-plugin": "^8.58.1", - "@typescript-eslint/parser": "^8.58.1", "chai": "^4.3.6", "chai-as-promised": "^7.1.1", "conventional-changelog-conventionalcommits": "5.0.0", - "cross-env": "^10.1.0", "cz-conventional-changelog": "3.3.0", - "eslint": "^9.39.4", "husky": "8.0.2", "knip": "2.43.0", "mocha": "^11.7.5", diff --git a/src/@types/adapters.ts b/src/@types/adapters.ts index 0e491c3a..b8c06eab 100644 --- a/src/@types/adapters.ts +++ b/src/@types/adapters.ts @@ -21,7 +21,7 @@ export type IWebSocketAdapter = EventEmitter & { export interface ICacheAdapter { getKey(key: string): Promise hasKey(key: string): Promise - setKey(key: string, value: string): Promise + setKey(key: string, value: string, expirySeconds?: number): Promise addToSortedSet(key: string, set: Record | Record[]): Promise removeRangeByScoreFromSortedSet(key: string, min: number, max: number): Promise getRangeFromSortedSet(key: string, start: number, stop: number): Promise diff --git a/src/adapters/redis-adapter.ts b/src/adapters/redis-adapter.ts index fcb5a39e..e2df49ba 100644 --- a/src/adapters/redis-adapter.ts +++ b/src/adapters/redis-adapter.ts @@ -42,7 +42,7 @@ export class RedisAdapter implements ICacheAdapter { } private onClientError(error: Error) { - console.error('Unable to connect to Redis.', error) + debug('Unable to connect to Redis.', error) // throw error } @@ -58,9 +58,12 @@ export class RedisAdapter implements ICacheAdapter { return this.client.get(key) } - public async setKey(key: string, value: string): Promise { + public async setKey(key: string, value: string, expirySeconds?: number): Promise { await this.connection - debug('get %s key', key) + debug('set %s key', key) + if (typeof expirySeconds === 'number') { + return 'OK' === await this.client.set(key, value, { EX: expirySeconds }) + } return 'OK' === await this.client.set(key, value) } diff --git a/src/constants/caching.ts b/src/constants/caching.ts new file mode 100644 index 00000000..7bd6e556 --- /dev/null +++ b/src/constants/caching.ts @@ -0,0 +1,5 @@ +export enum CacheAdmissionState { + ADMITTED = 'admitted', + BLOCKED_NOT_ADMITTED = 'blocked_not_admitted', + BLOCKED_INSUFFICIENT_BALANCE = 'blocked_insufficient_balance', +} diff --git a/src/controllers/invoices/post-invoice-controller.ts b/src/controllers/invoices/post-invoice-controller.ts index b8db3621..96d7f6b4 100644 --- a/src/controllers/invoices/post-invoice-controller.ts +++ b/src/controllers/invoices/post-invoice-controller.ts @@ -95,7 +95,7 @@ export class PostInvoiceController implements IController { } else if (/^npub1/.test(pubkeyRaw)) { try { pubkey = fromBech32(pubkeyRaw) - } catch (error) { + } catch (_error) { response .status(400) .setHeader('content-type', 'text/plain; charset=utf8') diff --git a/src/factories/message-handler-factory.ts b/src/factories/message-handler-factory.ts index e26d0511..b059875b 100644 --- a/src/factories/message-handler-factory.ts +++ b/src/factories/message-handler-factory.ts @@ -1,13 +1,23 @@ +import { ICacheAdapter, IWebSocketAdapter } from '../@types/adapters' import { IEventRepository, INip05VerificationRepository, IUserRepository } from '../@types/repositories' import { IncomingMessage, MessageType } from '../@types/messages' import { createSettings } from './settings-factory' import { EventMessageHandler } from '../handlers/event-message-handler' import { eventStrategyFactory } from './event-strategy-factory' -import { IWebSocketAdapter } from '../@types/adapters' +import { getCacheClient } from '../cache/client' +import { RedisAdapter } from '../adapters/redis-adapter' import { slidingWindowRateLimiterFactory } from './rate-limiter-factory' import { SubscribeMessageHandler } from '../handlers/subscribe-message-handler' import { UnsubscribeMessageHandler } from '../handlers/unsubscribe-message-handler' +let cacheAdapter: ICacheAdapter | undefined = undefined +const getCache = (): ICacheAdapter => { + if (!cacheAdapter) { + cacheAdapter = new RedisAdapter(getCacheClient()) + } + return cacheAdapter +} + export const messageHandlerFactory = ( eventRepository: IEventRepository, userRepository: IUserRepository, @@ -24,6 +34,7 @@ export const messageHandlerFactory = ( createSettings, slidingWindowRateLimiterFactory, nip05VerificationRepository, + getCache(), ) } case MessageType.REQ: diff --git a/src/handlers/event-message-handler.ts b/src/handlers/event-message-handler.ts index 73e7970d..7582d984 100644 --- a/src/handlers/event-message-handler.ts +++ b/src/handlers/event-message-handler.ts @@ -26,9 +26,11 @@ import { } from '../utils/event' import { IEventRepository, INip05VerificationRepository, IUserRepository } from '../@types/repositories' import { IEventStrategy, IMessageHandler } from '../@types/message-handlers' +import { CacheAdmissionState } from '../constants/caching' import { createCommandResult } from '../utils/messages' import { createLogger } from '../factories/logger-factory' import { Factory } from '../@types/base' +import { ICacheAdapter } from '../@types/adapters' import { IncomingEventMessage } from '../@types/messages' import { IRateLimiter } from '../@types/utils' import { IWebSocketAdapter } from '../@types/adapters' @@ -46,6 +48,7 @@ export class EventMessageHandler implements IMessageHandler { private readonly settings: () => Settings, private readonly slidingWindowRateLimiter: Factory, private readonly nip05VerificationRepository: INip05VerificationRepository, + private readonly cache: ICacheAdapter, ) {} public async handleMessage(message: IncomingEventMessage): Promise { @@ -112,8 +115,7 @@ export class EventMessageHandler implements IMessageHandler { try { await strategy.execute(event) this.processNip05Metadata(event) - } catch (error) { - console.error('error handling message', message, error) + } catch (_error) { this.webSocket.emit(WebSocketAdapterEvent.Message, createCommandResult(event.id, false, 'error: unable to process event')) } } @@ -341,17 +343,44 @@ export class EventMessageHandler implements IMessageHandler { return } - // const hasKey = await this.cache.hasKey(`${event.pubkey}:is-admitted`) - // TODO: use cache + const cacheKey = `${event.pubkey}:is-admitted` + + try { + const cachedValue = await this.cache.getKey(cacheKey) + if (cachedValue === CacheAdmissionState.ADMITTED) { + debug('cache hit for %s admission: admitted', event.pubkey) + return + } + if (cachedValue === CacheAdmissionState.BLOCKED_NOT_ADMITTED) { + debug('cache hit for %s admission: blocked', event.pubkey) + return 'blocked: pubkey not admitted' + } + if (cachedValue === CacheAdmissionState.BLOCKED_INSUFFICIENT_BALANCE) { + debug('cache hit for %s admission: insufficient balance', event.pubkey) + return 'blocked: insufficient balance' + } + } catch (error) { + debug('cache error for %s: %o', event.pubkey, error) + } + const user = await this.userRepository.findByPubkey(event.pubkey) if (!user || !user.isAdmitted) { + this.cacheSet(cacheKey, CacheAdmissionState.BLOCKED_NOT_ADMITTED, 60) return 'blocked: pubkey not admitted' } const minBalance = currentSettings.limits?.event?.pubkey?.minBalance ?? 0n if (minBalance > 0n && user.balance < minBalance) { + this.cacheSet(cacheKey, CacheAdmissionState.BLOCKED_INSUFFICIENT_BALANCE, 60) return 'blocked: insufficient balance' } + + this.cacheSet(cacheKey, CacheAdmissionState.ADMITTED, 300) + } + + private cacheSet(key: string, value: string, ttl: number): void { + this.cache.setKey(key, value, ttl) + .catch((error) => debug('unable to cache %s: %o', key, error)) } protected addExpirationMetadata(event: Event): Event | ExpiringEvent { diff --git a/src/handlers/request-handlers/with-controller-request-handler.ts b/src/handlers/request-handlers/with-controller-request-handler.ts index 52f55ebc..e7465251 100644 --- a/src/handlers/request-handlers/with-controller-request-handler.ts +++ b/src/handlers/request-handlers/with-controller-request-handler.ts @@ -9,7 +9,7 @@ export const withController = (controllerFactory: Factory) => async ) => { try { return await controllerFactory().handleRequest(request, response) - } catch (error) { + } catch (_error) { response .status(500) .setHeader('content-type', 'text/plain') diff --git a/src/payments-processors/lnbits-payment-processor.ts b/src/payments-processors/lnbits-payment-processor.ts index fc70aa6a..4f7b1e2e 100644 --- a/src/payments-processors/lnbits-payment-processor.ts +++ b/src/payments-processors/lnbits-payment-processor.ts @@ -57,7 +57,9 @@ export class LNbitsPaymentsProcessor implements IPaymentsProcessor { invoice.pubkey = data.details.extra.internalId invoice.bolt11 = data.details.bolt11 invoice.amountRequested = BigInt(Math.floor(data.details.amount / 1000)) - if (data.paid) invoice.amountPaid = BigInt(Math.floor(data.details.amount / 1000)) + if (data.paid) { + invoice.amountPaid = BigInt(Math.floor(data.details.amount / 1000)) + } invoice.unit = InvoiceUnit.SATS invoice.status = data.paid?InvoiceStatus.COMPLETED:InvoiceStatus.PENDING invoice.description = data.details.memo diff --git a/src/tor/client.ts b/src/tor/client.ts index 5e5206e6..b3fd89e1 100644 --- a/src/tor/client.ts +++ b/src/tor/client.ts @@ -36,7 +36,7 @@ export const getTorClient = async () => { client = new Tor(config) try{ await client.connect() - }catch(error){ + }catch(_error){ client = undefined } debug('connected') diff --git a/src/utils/event.ts b/src/utils/event.ts index 50ab0b73..2d6bc2ca 100644 --- a/src/utils/event.ts +++ b/src/utils/event.ts @@ -231,7 +231,9 @@ export const isExpiredEvent = (event: Event): boolean => { export const getEventExpiration = (event: Event): number | undefined => { const [, rawExpirationTime] = event.tags.find((tag) => tag.length >= 2 && tag[0] === EventTags.Expiration) ?? [] - if (!rawExpirationTime) return + if (!rawExpirationTime) { + return + } const expirationTime = Number(rawExpirationTime) diff --git a/src/utils/nip44.ts b/src/utils/nip44.ts index dd7edfde..3d7b6b11 100644 --- a/src/utils/nip44.ts +++ b/src/utils/nip44.ts @@ -45,8 +45,12 @@ function getMessageKeys( conversationKey: Buffer, nonce: Buffer, ): { chachaKey: Buffer; chachaNonce: Buffer; hmacKey: Buffer } { - if (conversationKey.length !== 32) throw new Error('invalid conversation_key length') - if (nonce.length !== 32) throw new Error('invalid nonce length') + if (conversationKey.length !== 32) { + throw new Error('invalid conversation_key length') + } + if (nonce.length !== 32) { + throw new Error('invalid nonce length') + } const keys = hkdfExpand(conversationKey, nonce, 76) return { @@ -57,7 +61,9 @@ function getMessageKeys( } function calcPaddedLen(unpaddedLen: number): number { - if (unpaddedLen <= 32) return 32 + if (unpaddedLen <= 32) { + return 32 + } const nextPower = 1 << (Math.floor(Math.log2(unpaddedLen - 1)) + 1) const chunk = nextPower <= 256 ? 32 : nextPower / 8 return chunk * (Math.floor((unpaddedLen - 1) / chunk) + 1) @@ -116,14 +122,22 @@ export function nip44Encrypt( * Validates version byte, payload sizes, and MAC before decrypting. */ export function nip44Decrypt(payload: string, conversationKey: Buffer): string { - if (!payload || payload[0] === '#') throw new Error('unknown version') - if (payload.length < 132 || payload.length > 87472) throw new Error('invalid payload size') + if (!payload || payload[0] === '#') { + throw new Error('unknown version') + } + if (payload.length < 132 || payload.length > 87472) { + throw new Error('invalid payload size') + } const data = Buffer.from(payload, 'base64') - if (data.length < 99 || data.length > 65603) throw new Error('invalid data size') + if (data.length < 99 || data.length > 65603) { + throw new Error('invalid data size') + } const version = data[0] - if (version !== 2) throw new Error(`unknown version ${version}`) + if (version !== 2) { + throw new Error(`unknown version ${version}`) + } const nonce = data.subarray(1, 33) const ciphertext = data.subarray(33, data.length - 32) @@ -132,7 +146,9 @@ export function nip44Decrypt(payload: string, conversationKey: Buffer): string { const { chachaKey, chachaNonce, hmacKey } = getMessageKeys(conversationKey, nonce) const expectedMac = createHmac('sha256', hmacKey).update(nonce).update(ciphertext).digest() - if (!timingSafeEqual(expectedMac, mac)) throw new Error('invalid MAC') + if (!timingSafeEqual(expectedMac, mac)) { + throw new Error('invalid MAC') + } const iv = Buffer.concat([Buffer.alloc(4), chachaNonce]) const decipher = createDecipheriv('chacha20', chachaKey, iv) @@ -148,8 +164,12 @@ export function nip44Decrypt(payload: string, conversationKey: Buffer): string { const BASE64_RE = /^[A-Za-z0-9+/]*={0,2}$/ export function validateNip44Payload(payload: string): string | undefined { - if (!payload || payload[0] === '#') return 'unsupported encryption version' - if (payload.length < 132 || payload.length > 87472) return 'invalid payload size' + if (!payload || payload[0] === '#') { + return 'unsupported encryption version' + } + if (payload.length < 132 || payload.length > 87472) { + return 'invalid payload size' + } if (payload.length % 4 !== 0 || !BASE64_RE.test(payload)) { return 'payload is not valid base64' @@ -157,8 +177,12 @@ export function validateNip44Payload(payload: string): string | undefined { const data = Buffer.from(payload, 'base64') - if (data.length < 99 || data.length > 65603) return 'invalid decoded payload size' - if (data[0] !== 2) return `unsupported encryption version ${data[0]}` + if (data.length < 99 || data.length > 65603) { + return 'invalid decoded payload size' + } + if (data[0] !== 2) { + return `unsupported encryption version ${data[0]}` + } return undefined } diff --git a/test/unit/factories/message-handler-factory.spec.ts b/test/unit/factories/message-handler-factory.spec.ts index ea19038f..65fddd05 100644 --- a/test/unit/factories/message-handler-factory.spec.ts +++ b/test/unit/factories/message-handler-factory.spec.ts @@ -8,6 +8,8 @@ import { IWebSocketAdapter } from '../../../src/@types/adapters' import { messageHandlerFactory } from '../../../src/factories/message-handler-factory' import { SubscribeMessageHandler } from '../../../src/handlers/subscribe-message-handler' import { UnsubscribeMessageHandler } from '../../../src/handlers/unsubscribe-message-handler' +import * as cacheModule from '../../../src/cache/client' +import sinon from 'sinon' describe('messageHandlerFactory', () => { let event: Event @@ -17,8 +19,16 @@ describe('messageHandlerFactory', () => { let message: IncomingMessage let adapter: IWebSocketAdapter let factory + let sandbox: sinon.SinonSandbox beforeEach(() => { + sandbox = sinon.createSandbox() + sandbox.stub(cacheModule, 'getCacheClient').returns({ + connect: async () => {}, + on: function() { return this }, + once: function() { return this }, + removeListener: function() { return this } + } as any) eventRepository = {} as any userRepository = {} as any nip05VerificationRepository = {} as any @@ -29,6 +39,10 @@ describe('messageHandlerFactory', () => { factory = messageHandlerFactory(eventRepository, userRepository, nip05VerificationRepository) }) + afterEach(() => { + sandbox.restore() + }) + it('returns EventMessageHandler when given an EVENT message', () => { message = [ MessageType.EVENT, diff --git a/test/unit/handlers/event-message-handler.spec.ts b/test/unit/handlers/event-message-handler.spec.ts index 33a17b2c..0b124639 100644 --- a/test/unit/handlers/event-message-handler.spec.ts +++ b/test/unit/handlers/event-message-handler.spec.ts @@ -11,6 +11,7 @@ chai.use(chaiAsPromised) import { EventLimits, Settings } from '../../../src/@types/settings' import { identifyEvent, signEvent } from '../../../src/utils/event' import { IncomingEventMessage, MessageType } from '../../../src/@types/messages' +import { CacheAdmissionState } from '../../../src/constants/caching' import { Event } from '../../../src/@types/event' import { EventKinds } from '../../../src/constants/base' import { EventMessageHandler } from '../../../src/handlers/event-message-handler' @@ -99,6 +100,7 @@ describe('EventMessageHandler', () => { }) as any, () => ({ hit: async () => false }), {} as any, + { hasKey: async () => false, setKey: async () => true } as any, ) }) @@ -274,6 +276,7 @@ describe('EventMessageHandler', () => { () => settings, () => ({ hit: async () => false }), {} as any, + { hasKey: async () => false, setKey: async () => true } as any, ) }) @@ -804,6 +807,7 @@ describe('EventMessageHandler', () => { () => settings, () => ({ hit: rateLimiterHitStub }), {} as any, + { hasKey: async () => false, setKey: async () => true } as any, ) }) @@ -1018,6 +1022,7 @@ describe('EventMessageHandler', () => { let webSocket: IWebSocketAdapter let getRelayPublicKeyStub: SinonStub let userRepositoryFindByPubkeyStub: SinonStub + let cacheStub: any beforeEach(() => { settings = { @@ -1066,6 +1071,11 @@ describe('EventMessageHandler', () => { findByPubkey: userRepositoryFindByPubkeyStub, isVanished: async () => false, } as any + cacheStub = { + hasKey: sandbox.stub().resolves(false), + getKey: sandbox.stub().resolves(null), + setKey: sandbox.stub().resolves(true), + } handler = new EventMessageHandler( webSocket, () => null, @@ -1074,6 +1084,7 @@ describe('EventMessageHandler', () => { () => settings, () => ({ hit: async () => false }), {} as any, + cacheStub, ) }) @@ -1175,6 +1186,52 @@ describe('EventMessageHandler', () => { return expect((handler as any).isUserAdmitted(event)).to.eventually.be.undefined }) + + describe('caching', () => { + it('fulfills with undefined and uses cache hit for admitted user without hitting DB', async () => { + cacheStub.getKey.resolves(CacheAdmissionState.ADMITTED) + + await expect((handler as any).isUserAdmitted(event)).to.eventually.be.undefined + expect(userRepositoryFindByPubkeyStub).not.to.have.been.called + }) + + it('fulfills with reason and uses cache hit for blocked user without hitting DB', async () => { + cacheStub.getKey.resolves(CacheAdmissionState.BLOCKED_NOT_ADMITTED) + + await expect((handler as any).isUserAdmitted(event)).to.eventually.equal('blocked: pubkey not admitted') + expect(userRepositoryFindByPubkeyStub).not.to.have.been.called + }) + + it('fulfills with reason and uses cache hit for insufficient balance without hitting DB', async () => { + cacheStub.getKey.resolves(CacheAdmissionState.BLOCKED_INSUFFICIENT_BALANCE) + + await expect((handler as any).isUserAdmitted(event)).to.eventually.equal('blocked: insufficient balance') + expect(userRepositoryFindByPubkeyStub).not.to.have.been.called + }) + + it('caches blocked status with 60s ttl when user is not found', async () => { + userRepositoryFindByPubkeyStub.resolves(undefined) + + await (handler as any).isUserAdmitted(event) + expect(cacheStub.setKey).to.have.been.calledWith(`${event.pubkey}:is-admitted`, CacheAdmissionState.BLOCKED_NOT_ADMITTED, 60) + }) + + it('caches insufficient balance status with 60s ttl when user balance is too low', async () => { + settings.limits.event.pubkey.minBalance = 100n + userRepositoryFindByPubkeyStub.resolves({ isAdmitted: true, balance: 50n }) + + await (handler as any).isUserAdmitted(event) + expect(cacheStub.setKey).to.have.been.calledWith(`${event.pubkey}:is-admitted`, CacheAdmissionState.BLOCKED_INSUFFICIENT_BALANCE, 60) + }) + + it('caches admitted status with 300s ttl when user is admitted and has balance', async () => { + settings.limits.event.pubkey.minBalance = 100n + userRepositoryFindByPubkeyStub.resolves({ isAdmitted: true, balance: 150n }) + + await (handler as any).isUserAdmitted(event) + expect(cacheStub.setKey).to.have.been.calledWith(`${event.pubkey}:is-admitted`, CacheAdmissionState.ADMITTED, 300) + }) + }) }) describe('checkNip05Verification', () => { @@ -1220,7 +1277,8 @@ describe('EventMessageHandler', () => { () => settings, () => ({ hit: async () => false }), nip05VerificationRepository, - ) + { hasKey: async () => false, setKey: async () => true, getKey: async () => null } as any, +) }) it('returns undefined if nip05 settings are not set', async () => { @@ -1389,7 +1447,409 @@ describe('EventMessageHandler', () => { () => settings, () => ({ hit: async () => false }), nip05VerificationRepository, - ) + { hasKey: async () => false, setKey: async () => true, getKey: async () => null } as any, +) + }) + + it('does nothing when nip05 settings are undefined', async () => { + settings.nip05 = undefined + event.kind = EventKinds.SET_METADATA + event.content = JSON.stringify({ nip05: 'alice@example.com' }) + + ;(handler as any).processNip05Metadata(event) + await new Promise((resolve) => setTimeout(resolve, 10)) + + expect(verifyStub).not.to.have.been.called + }) + + it('does nothing when nip05 mode is disabled', async () => { + settings.nip05.mode = 'disabled' + event.kind = EventKinds.SET_METADATA + event.content = JSON.stringify({ nip05: 'alice@example.com' }) + + ;(handler as any).processNip05Metadata(event) + await new Promise((resolve) => setTimeout(resolve, 10)) + + expect(verifyStub).not.to.have.been.called + }) + + it('does nothing for non-kind-0 events', async () => { + event.kind = EventKinds.TEXT_NOTE + event.content = JSON.stringify({ nip05: 'alice@example.com' }) + + ;(handler as any).processNip05Metadata(event) + await new Promise((resolve) => setTimeout(resolve, 10)) + + expect(verifyStub).not.to.have.been.called + }) + + it('deletes verification when kind-0 has no nip05 in content', async () => { + event.kind = EventKinds.SET_METADATA + event.content = JSON.stringify({ name: 'alice' }) + + ;(handler as any).processNip05Metadata(event) + await new Promise((resolve) => setTimeout(resolve, 10)) + + expect(nip05VerificationRepository.deleteByPubkey).to.have.been.calledOnceWithExactly(event.pubkey) + expect(verifyStub).not.to.have.been.called + }) + + it('does nothing when nip05 identifier is unparseable', async () => { + event.kind = EventKinds.SET_METADATA + event.content = JSON.stringify({ nip05: 'invalid-no-at-sign' }) + + ;(handler as any).processNip05Metadata(event) + await new Promise((resolve) => setTimeout(resolve, 10)) + + expect(verifyStub).not.to.have.been.called + expect(nip05VerificationRepository.deleteByPubkey).not.to.have.been.called + }) + + it('does nothing when domain is not allowed', async () => { + settings.nip05.domainBlacklist = ['blocked.com'] + event.kind = EventKinds.SET_METADATA + event.content = JSON.stringify({ nip05: 'alice@blocked.com' }) + + ;(handler as any).processNip05Metadata(event) + await new Promise((resolve) => setTimeout(resolve, 10)) + + expect(verifyStub).not.to.have.been.called + }) + + it('verifies and upserts on successful verification', async () => { + nip05VerificationRepository.findByPubkey.resolves(undefined) + verifyStub.resolves({ status: 'verified' }) + event.kind = EventKinds.SET_METADATA + event.content = JSON.stringify({ nip05: 'alice@example.com' }) + + ;(handler as any).processNip05Metadata(event) + await new Promise((resolve) => setTimeout(resolve, 10)) + + expect(verifyStub).to.have.been.calledOnceWithExactly('alice@example.com', event.pubkey) + expect(nip05VerificationRepository.upsert).to.have.been.calledOnce + + const upsertArg = nip05VerificationRepository.upsert.firstCall.args[0] + expect(upsertArg.pubkey).to.equal(event.pubkey) + expect(upsertArg.nip05).to.equal('alice@example.com') + expect(upsertArg.domain).to.equal('example.com') + expect(upsertArg.isVerified).to.be.true + expect(upsertArg.failureCount).to.equal(0) + expect(upsertArg.lastVerifiedAt).to.be.an.instanceOf(Date) + }) + + it('upserts with unverified state and nulls lastVerifiedAt on definitive mismatch', async () => { + nip05VerificationRepository.findByPubkey.resolves(undefined) + verifyStub.resolves({ status: 'mismatch' }) + event.kind = EventKinds.SET_METADATA + event.content = JSON.stringify({ nip05: 'alice@example.com' }) + + ;(handler as any).processNip05Metadata(event) + await new Promise((resolve) => setTimeout(resolve, 10)) + + expect(verifyStub).to.have.been.calledOnce + expect(nip05VerificationRepository.upsert).to.have.been.calledOnce + + const upsertArg = nip05VerificationRepository.upsert.firstCall.args[0] + expect(upsertArg.isVerified).to.be.false + expect(upsertArg.failureCount).to.equal(1) + expect(upsertArg.lastVerifiedAt).to.be.null + }) + + it('increments failureCount from existing row on definitive mismatch', async () => { + const priorVerifiedAt = new Date(Date.now() - 1000) + nip05VerificationRepository.findByPubkey.resolves({ + pubkey: event.pubkey, + nip05: 'alice@example.com', + domain: 'example.com', + isVerified: true, + lastVerifiedAt: priorVerifiedAt, + lastCheckedAt: priorVerifiedAt, + failureCount: 2, + createdAt: priorVerifiedAt, + updatedAt: priorVerifiedAt, + }) + verifyStub.resolves({ status: 'mismatch' }) + event.kind = EventKinds.SET_METADATA + event.content = JSON.stringify({ nip05: 'alice@example.com' }) + + ;(handler as any).processNip05Metadata(event) + await new Promise((resolve) => setTimeout(resolve, 10)) + + const upsertArg = nip05VerificationRepository.upsert.firstCall.args[0] + expect(upsertArg.failureCount).to.equal(3) + expect(upsertArg.isVerified).to.be.false + expect(upsertArg.lastVerifiedAt).to.be.null + }) + + it('preserves prior isVerified/lastVerifiedAt on transient error', async () => { + const priorVerifiedAt = new Date(Date.now() - 1000) + nip05VerificationRepository.findByPubkey.resolves({ + pubkey: event.pubkey, + nip05: 'alice@example.com', + domain: 'example.com', + isVerified: true, + lastVerifiedAt: priorVerifiedAt, + lastCheckedAt: priorVerifiedAt, + failureCount: 1, + createdAt: priorVerifiedAt, + updatedAt: priorVerifiedAt, + }) + verifyStub.resolves({ status: 'error', reason: 'ETIMEDOUT' }) + event.kind = EventKinds.SET_METADATA + event.content = JSON.stringify({ nip05: 'alice@example.com' }) + + ;(handler as any).processNip05Metadata(event) + await new Promise((resolve) => setTimeout(resolve, 10)) + + const upsertArg = nip05VerificationRepository.upsert.firstCall.args[0] + expect(upsertArg.isVerified).to.be.true + expect(upsertArg.lastVerifiedAt).to.equal(priorVerifiedAt) + expect(upsertArg.failureCount).to.equal(2) + expect(upsertArg.lastCheckedAt).to.be.an.instanceOf(Date) + }) + + it('handles verification errors gracefully (thrown by verifier)', async () => { + nip05VerificationRepository.findByPubkey.resolves(undefined) + verifyStub.rejects(new Error('network error')) + event.kind = EventKinds.SET_METADATA + event.content = JSON.stringify({ nip05: 'alice@example.com' }) + + ;(handler as any).processNip05Metadata(event) + await new Promise((resolve) => setTimeout(resolve, 10)) + + expect(nip05VerificationRepository.upsert).not.to.have.been.called + }) + + it('works correctly in passive mode', async () => { + settings.nip05.mode = 'passive' + nip05VerificationRepository.findByPubkey.resolves(undefined) + verifyStub.resolves({ status: 'verified' }) + event.kind = EventKinds.SET_METADATA + event.content = JSON.stringify({ nip05: 'alice@example.com' }) + + ;(handler as any).processNip05Metadata(event) + await new Promise((resolve) => setTimeout(resolve, 10)) + + expect(verifyStub).to.have.been.calledOnce + expect(nip05VerificationRepository.upsert).to.have.been.calledOnce + }) + }) + + describe('checkNip05Verification', () => { + let settings: Settings + let nip05VerificationRepository: any + let getRelayPublicKeyStub: Sinon.SinonStub + + beforeEach(() => { + settings = { + info: { + relay_url: 'relay_url', + }, + nip05: { + mode: 'enabled', + verifyExpiration: 86400000, + verifyUpdateFrequency: 3600000, + maxConsecutiveFailures: 10, + domainWhitelist: [], + domainBlacklist: [], + }, + } as any + event = { + content: 'hello', + created_at: 1665546189, + id: 'f'.repeat(64), + kind: 1, + pubkey: 'f'.repeat(64), + sig: 'f'.repeat(128), + tags: [], + } + nip05VerificationRepository = { + findByPubkey: sandbox.stub(), + upsert: sandbox.stub(), + deleteByPubkey: sandbox.stub(), + findPendingVerifications: sandbox.stub(), + } + getRelayPublicKeyStub = sandbox.stub(EventMessageHandler.prototype, 'getRelayPublicKey' as any) + handler = new EventMessageHandler( + {} as any, + () => null, + { hasActiveRequestToVanish: async () => false } as any, + userRepository, + () => settings, + () => ({ hit: async () => false }), + nip05VerificationRepository, + { hasKey: async () => false, setKey: async () => true, getKey: async () => null } as any, +) + }) + + it('returns undefined if nip05 settings are not set', async () => { + settings.nip05 = undefined + + return expect((handler as any).checkNip05Verification(event)).to.eventually.be.undefined + }) + + it('returns undefined if nip05 mode is disabled', async () => { + settings.nip05.mode = 'disabled' + + return expect((handler as any).checkNip05Verification(event)).to.eventually.be.undefined + }) + + it('returns undefined if nip05 mode is passive', async () => { + settings.nip05.mode = 'passive' + + return expect((handler as any).checkNip05Verification(event)).to.eventually.be.undefined + }) + + it('returns undefined for kind 0 events (SET_METADATA)', async () => { + event.kind = 0 + + return expect((handler as any).checkNip05Verification(event)).to.eventually.be.undefined + }) + + it('returns undefined if event pubkey equals relay public key', async () => { + getRelayPublicKeyStub.returns(event.pubkey) + + return expect((handler as any).checkNip05Verification(event)).to.eventually.be.undefined + }) + + it('returns reason if no verification found for pubkey', async () => { + nip05VerificationRepository.findByPubkey.resolves(undefined) + + return expect((handler as any).checkNip05Verification(event)) + .to.eventually.equal('blocked: NIP-05 verification required') + }) + + it('returns reason if verification exists but has no lastVerifiedAt', async () => { + nip05VerificationRepository.findByPubkey.resolves({ + isVerified: false, + lastVerifiedAt: null, + domain: 'example.com', + }) + + return expect((handler as any).checkNip05Verification(event)) + .to.eventually.equal('blocked: NIP-05 verification required') + }) + + it('treats isVerified=true with null lastVerifiedAt as unverified (historical/bad data)', async () => { + nip05VerificationRepository.findByPubkey.resolves({ + isVerified: true, + lastVerifiedAt: null, + domain: 'example.com', + }) + + return expect((handler as any).checkNip05Verification(event)) + .to.eventually.equal('blocked: NIP-05 verification required') + }) + + it('returns reason if verification is expired', async () => { + const expired = new Date(Date.now() - 86400001) + nip05VerificationRepository.findByPubkey.resolves({ + isVerified: true, + lastVerifiedAt: expired, + domain: 'example.com', + }) + + return expect((handler as any).checkNip05Verification(event)) + .to.eventually.equal('blocked: NIP-05 verification expired') + }) + + it('returns undefined if verification is valid and not expired', async () => { + const recent = new Date(Date.now() - 1000) + nip05VerificationRepository.findByPubkey.resolves({ + isVerified: true, + lastVerifiedAt: recent, + domain: 'example.com', + }) + + return expect((handler as any).checkNip05Verification(event)).to.eventually.be.undefined + }) + + it('allows author when lastVerifiedAt is recent even if isVerified is false (transient re-check failure)', async () => { + const recent = new Date(Date.now() - 1000) + nip05VerificationRepository.findByPubkey.resolves({ + isVerified: false, + lastVerifiedAt: recent, + domain: 'example.com', + }) + + return expect((handler as any).checkNip05Verification(event)).to.eventually.be.undefined + }) + + it('returns reason if domain is blacklisted', async () => { + settings.nip05.domainBlacklist = ['spam.com'] + const recent = new Date(Date.now() - 1000) + nip05VerificationRepository.findByPubkey.resolves({ + isVerified: true, + lastVerifiedAt: recent, + domain: 'spam.com', + }) + + return expect((handler as any).checkNip05Verification(event)) + .to.eventually.equal('blocked: NIP-05 domain not allowed') + }) + + it('returns reason if domain is not in whitelist', async () => { + settings.nip05.domainWhitelist = ['allowed.com'] + const recent = new Date(Date.now() - 1000) + nip05VerificationRepository.findByPubkey.resolves({ + isVerified: true, + lastVerifiedAt: recent, + domain: 'other.com', + }) + + return expect((handler as any).checkNip05Verification(event)) + .to.eventually.equal('blocked: NIP-05 domain not allowed') + }) + + it('returns undefined if domain is in whitelist', async () => { + settings.nip05.domainWhitelist = ['allowed.com'] + const recent = new Date(Date.now() - 1000) + nip05VerificationRepository.findByPubkey.resolves({ + isVerified: true, + lastVerifiedAt: recent, + domain: 'allowed.com', + }) + + return expect((handler as any).checkNip05Verification(event)).to.eventually.be.undefined + }) + }) + + describe('processNip05Metadata', () => { + let settings: Settings + let nip05VerificationRepository: any + let verifyStub: Sinon.SinonStub + + beforeEach(() => { + settings = { + info: { + relay_url: 'relay_url', + }, + nip05: { + mode: 'enabled', + verifyExpiration: 86400000, + verifyUpdateFrequency: 3600000, + maxConsecutiveFailures: 10, + domainWhitelist: [], + domainBlacklist: [], + }, + } as any + nip05VerificationRepository = { + findByPubkey: sandbox.stub(), + upsert: sandbox.stub().resolves(1), + deleteByPubkey: sandbox.stub().resolves(1), + findPendingVerifications: sandbox.stub(), + } + verifyStub = sandbox.stub(nip05Utils, 'verifyNip05Identifier') + handler = new EventMessageHandler( + {} as any, + () => null, + { hasActiveRequestToVanish: async () => false } as any, + userRepository, + () => settings, + () => ({ hit: async () => false }), + nip05VerificationRepository, + { hasKey: async () => false, setKey: async () => true, getKey: async () => null } as any, +) }) it('does nothing when nip05 settings are undefined', async () => { diff --git a/test/unit/tor/onion.spec.ts b/test/unit/tor/onion.spec.ts index ec1d8a5d..f37310ca 100644 --- a/test/unit/tor/onion.spec.ts +++ b/test/unit/tor/onion.spec.ts @@ -104,8 +104,7 @@ describe('onion',()=>{ try{ client = await getTorClient() closeTorClient() - }catch(error){ - error + }catch(_error){ } expect(client).be.undefined }) @@ -118,8 +117,7 @@ describe('onion',()=>{ try{ client = await getTorClient() closeTorClient() - }catch(error){ - error + }catch(_error){ } expect(client).be.not.undefined }) @@ -135,7 +133,7 @@ describe('onion',()=>{ domain = await addOnion(80) closeTorClient() //domain = undefined - } catch (error) { + } catch (_error) { domain } expect(domain).be.undefined @@ -152,7 +150,7 @@ describe('onion',()=>{ try { domain = await addOnion(80,'}') closeTorClient() - } catch (error) { + } catch (_error) { domain } expect(domain).be.undefined @@ -169,7 +167,7 @@ describe('onion',()=>{ domain = await addOnion(80) closeTorClient() //domain = undefined - } catch (error) { + } catch (_error) { domain } console.log('domain: '+domain) @@ -185,7 +183,7 @@ describe('onion',()=>{ try { domain = await addOnion(80) closeTorClient() - } catch (error) { + } catch (_error) { domain } console.log('domain: '+domain) @@ -201,7 +199,7 @@ describe('onion',()=>{ try { domain = await addOnion(80) closeTorClient() - } catch (error) { + } catch (_error) { domain } console.log('domain: '+domain) diff --git a/test/unit/utils/template-cache.spec.ts b/test/unit/utils/template-cache.spec.ts index 22f8648b..0183f6be 100644 --- a/test/unit/utils/template-cache.spec.ts +++ b/test/unit/utils/template-cache.spec.ts @@ -15,7 +15,7 @@ describe('getTemplate', () => { }) afterEach(() => { - try { fs.unlinkSync(tmpFile) } catch (e) { /* ignore */ } + try { fs.unlinkSync(tmpFile) } catch (_e) { /* ignore */ } }) it('returns the file content', () => {