diff --git a/.gitignore b/.gitignore index a61cd08..66e5b75 100644 --- a/.gitignore +++ b/.gitignore @@ -103,6 +103,9 @@ web_modules/ # Optional npm cache directory .npm +# Optional package-lock.json file +package-lock.json + # Optional eslint cache .eslintcache diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2f8c7ac..c2670a2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,16 +11,22 @@ repos: hooks: - id: shellcheck -- repo: https://github.com/pre-commit/mirrors-eslint - rev: v10.0.3 +- repo: local + hooks: + - id: prettier + name: prettier + entry: npx prettier --write + language: system + files: "\\.(js|jsx|ts|tsx)$" + +- repo: local hooks: - id: eslint + name: eslint + entry: npx eslint --fix + language: system files: "\\.(js|jsx|ts|tsx)$" - exclude: node_modules/ -# Use stylelint (local) instead of the deprecated scss-lint Ruby gem which -# cannot parse modern Sass `@use` and module syntax. This invokes the -# project's installed `stylelint` via `npx` so the devDependency is used. - repo: local hooks: - id: stylelint diff --git a/.prettierrc.ts b/.prettierrc.ts new file mode 100644 index 0000000..fc73471 --- /dev/null +++ b/.prettierrc.ts @@ -0,0 +1,8 @@ +import { type Config } from "prettier"; + +const config: Config = { + trailingComma: "none", + printWidth: 120 +}; + +export default config; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 1d94ab4..0000000 --- a/package-lock.json +++ /dev/null @@ -1,3337 +0,0 @@ -{ - "name": "meshcore", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "meshcore", - "version": "0.0.1", - "license": "MIT", - "dependencies": { - "@noble/ciphers": "^2.1.1", - "@noble/curves": "^2.0.1", - "@noble/ed25519": "^3.0.0", - "@noble/hashes": "^2.0.1" - }, - "devDependencies": { - "@eslint/js": "^10.0.1", - "@vitest/coverage-v8": "^4.0.18", - "eslint": "^10.0.3", - "globals": "^17.4.0", - "tsup": "^8.5.1", - "typescript": "^5.9.3", - "typescript-eslint": "^8.57.0", - "vitest": "^4.0.18" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "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/eslint-utils/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", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "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.23.3", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz", - "integrity": "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^3.0.3", - "debug": "^4.3.1", - "minimatch": "^10.2.4" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.3.tgz", - "integrity": "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.1.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", - "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", - "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/@eslint/object-schema": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz", - "integrity": "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz", - "integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.1.1", - "levn": "^0.4.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.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/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@noble/ciphers": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.1.1.tgz", - "integrity": "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/curves": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", - "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "2.0.1" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/ed25519": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-3.0.0.tgz", - "integrity": "sha512-QyteqMNm0GLqfa5SoYbSC3+Pvykwpn95Zgth4MFVSMKBB75ELl9tX1LAVsN4c3HXOrakHsF2gL4zWDAYCcsnzg==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", - "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/esrecurse": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", - "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", - "dev": true, - "license": "MIT" - }, - "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/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/@typescript-eslint/eslint-plugin": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.0.tgz", - "integrity": "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/type-utils": "8.57.0", - "@typescript-eslint/utils": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.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.57.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.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.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.0.tgz", - "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", - "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.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.0.tgz", - "integrity": "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.57.0", - "@typescript-eslint/types": "^8.57.0", - "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.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.0.tgz", - "integrity": "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.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/tsconfig-utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.0.tgz", - "integrity": "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==", - "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.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.0.tgz", - "integrity": "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/utils": "8.57.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.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.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.0.tgz", - "integrity": "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==", - "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.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.0.tgz", - "integrity": "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.57.0", - "@typescript-eslint/tsconfig-utils": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.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.0.0" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.0.tgz", - "integrity": "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.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.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.0.tgz", - "integrity": "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.57.0", - "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/@vitest/coverage-v8": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz", - "integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.18", - "ast-v8-to-istanbul": "^0.3.10", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.1", - "obug": "^2.1.1", - "std-env": "^3.10.0", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "4.0.18", - "vitest": "4.0.18" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", - "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.18", - "@vitest/utils": "4.0.18", - "chai": "^6.2.1", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", - "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.0.18", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", - "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", - "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.0.18", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", - "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.0.18", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", - "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", - "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.0.18", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "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/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/ast-v8-to-istanbul": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz", - "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" - } - }, - "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/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/bundle-require": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", - "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "load-tsconfig": "^0.2.3" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "esbuild": ">=0.18" - } - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "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/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/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" - } - }, - "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": "10.0.3", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.3.tgz", - "integrity": "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.3", - "@eslint/config-helpers": "^0.5.2", - "@eslint/core": "^1.1.1", - "@eslint/plugin-kit": "^0.6.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", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.1.1", - "esquery": "^1.7.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", - "minimatch": "^10.2.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", - "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "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/espree": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", - "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "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/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.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/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "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/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "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/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fix-dts-default-cjs-exports": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", - "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.17", - "mlly": "^1.7.4", - "rollup": "^4.34.8" - } - }, - "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.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "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/globals": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", - "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, - "license": "MIT" - }, - "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-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/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/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/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/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/load-tsconfig": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", - "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/magicast": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", - "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "source-map-js": "^1.2.1" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mlly": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.1.tgz", - "integrity": "sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.16.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.3" - } - }, - "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/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "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/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "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/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-load-config": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", - "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.1.1" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "jiti": ">=1.21.0", - "postcss": ">=8.0.9", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "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/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/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" - } - }, - "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/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true, - "license": "MIT" - }, - "node_modules/sucrase": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", - "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "tinyglobby": "^0.2.11", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyrainbow": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", - "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/tsup": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", - "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-require": "^5.1.0", - "cac": "^6.7.14", - "chokidar": "^4.0.3", - "consola": "^3.4.0", - "debug": "^4.4.0", - "esbuild": "^0.27.0", - "fix-dts-default-cjs-exports": "^1.0.0", - "joycon": "^3.1.1", - "picocolors": "^1.1.1", - "postcss-load-config": "^6.0.1", - "resolve-from": "^5.0.0", - "rollup": "^4.34.8", - "source-map": "^0.7.6", - "sucrase": "^3.35.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.11", - "tree-kill": "^1.2.2" - }, - "bin": { - "tsup": "dist/cli-default.js", - "tsup-node": "dist/cli-node.js" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@microsoft/api-extractor": "^7.36.0", - "@swc/core": "^1", - "postcss": "^8.4.12", - "typescript": ">=4.5.0" - }, - "peerDependenciesMeta": { - "@microsoft/api-extractor": { - "optional": true - }, - "@swc/core": { - "optional": true - }, - "postcss": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "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/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.0.tgz", - "integrity": "sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.57.0", - "@typescript-eslint/parser": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/utils": "8.57.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.0.0" - } - }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "dev": true, - "license": "MIT" - }, - "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/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitest": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", - "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@vitest/expect": "4.0.18", - "@vitest/mocker": "4.0.18", - "@vitest/pretty-format": "4.0.18", - "@vitest/runner": "4.0.18", - "@vitest/snapshot": "4.0.18", - "@vitest/spy": "4.0.18", - "@vitest/utils": "4.0.18", - "es-module-lexer": "^1.7.0", - "expect-type": "^1.2.2", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^3.10.0", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.18", - "@vitest/browser-preview": "4.0.18", - "@vitest/browser-webdriverio": "4.0.18", - "@vitest/ui": "4.0.18", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/vitest/node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/package.json b/package.json index 828e43c..2adf610 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "@hamradio/meshcore", + "type": "module", "version": "1.2.2", "description": "MeshCore protocol support for Typescript", "keywords": [ @@ -37,6 +38,7 @@ "prepare": "npm run build" }, "dependencies": { + "@hamradio/packet": "file:../packet.js/hamradio-packet-1.0.4.tgz", "@noble/ciphers": "^2.1.1", "@noble/curves": "^2.0.1", "@noble/ed25519": "^3.0.0", @@ -47,6 +49,7 @@ "@vitest/coverage-v8": "^4.0.18", "eslint": "^10.0.3", "globals": "^17.4.0", + "prettier": "3.8.1", "tsup": "^8.5.1", "typescript": "^5.9.3", "typescript-eslint": "^8.57.0", diff --git a/src/crypto.ts b/src/crypto.ts index f105de4..222d0e8 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -1,10 +1,10 @@ -import { ed25519, x25519 } from '@noble/curves/ed25519.js'; +import { ed25519, x25519 } from "@noble/curves/ed25519.js"; import { sha256 } from "@noble/hashes/sha2.js"; -import { hmac } from '@noble/hashes/hmac.js'; -import { ecb } from '@noble/ciphers/aes.js'; -import { bytesToHex, equalBytes, hexToBytes } from "./parser"; -import { IPublicKey, ISharedSecret, IStaticSecret } from './crypto.types'; -import { NodeHash } from './identity.types'; +import { hmac } from "@noble/hashes/hmac.js"; +import { ecb } from "@noble/ciphers/aes.js"; +import { bytesToHex, equalBytes, hexToBytes } from "@hamradio/packet"; +import { IPublicKey, ISharedSecret, IStaticSecret } from "./crypto.types"; +import { NodeHash } from "./identity.types"; const PUBLIC_KEY_SIZE = 32; const SEED_SIZE = 32; @@ -14,18 +14,21 @@ const SIGNATURE_SIZE = 64; const STATIC_SECRET_SIZE = 32; // The "Public" group is a special group that all nodes are implicitly part of. -const publicSecret = hexToBytes("8b3387e9c5cdea6ac9e5edbaa115cd72", 16); +const publicSecret = hexToBytes("8b3387e9c5cdea6ac9e5edbaa115cd72"); export class PublicKey implements IPublicKey { public key: Uint8Array; constructor(key: Uint8Array | string) { - if (typeof key === 'string') { - this.key = hexToBytes(key, PUBLIC_KEY_SIZE); + if (typeof key === "string") { + this.key = hexToBytes(key); } else if (key instanceof Uint8Array) { this.key = key; } else { - throw new Error('Invalid type for PublicKey constructor'); + throw new Error("Invalid type for PublicKey constructor"); + } + if (this.key.length !== PUBLIC_KEY_SIZE) { + throw new Error(`Invalid public key length: expected ${PUBLIC_KEY_SIZE} bytes, got ${this.key.length}`); } } @@ -47,10 +50,15 @@ export class PublicKey implements IPublicKey { otherKey = other.toBytes(); } else if (other instanceof Uint8Array) { otherKey = other; - } else if (typeof other === 'string') { - otherKey = hexToBytes(other, PUBLIC_KEY_SIZE); + } else if (typeof other === "string") { + otherKey = hexToBytes(other); } else { - throw new Error('Invalid type for PublicKey comparison'); + throw new Error("Invalid type for PublicKey comparison"); + } + if (otherKey.length !== PUBLIC_KEY_SIZE) { + throw new Error( + `Invalid public key length for comparison: expected ${PUBLIC_KEY_SIZE} bytes, got ${otherKey.length}` + ); } return equalBytes(this.key, otherKey); } @@ -61,6 +69,14 @@ export class PublicKey implements IPublicKey { } return ed25519.verify(signature, message, this.key); } + + public static fromBytes(key: Uint8Array): PublicKey { + return new PublicKey(key); + } + + public static fromString(key: string): PublicKey { + return new PublicKey(key); + } } export class PrivateKey { @@ -68,8 +84,8 @@ export class PrivateKey { private publicKey: PublicKey; constructor(seed: Uint8Array | string) { - if (typeof seed === 'string') { - seed = hexToBytes(seed, SEED_SIZE); + if (typeof seed === "string") { + seed = hexToBytes(seed); } if (seed.length !== SEED_SIZE) { throw new Error(`Invalid seed length: expected ${SEED_SIZE} bytes, got ${seed.length}`); @@ -102,10 +118,10 @@ export class PrivateKey { otherPublicKey = other; } else if (other instanceof Uint8Array) { otherPublicKey = new PublicKey(other); - } else if (typeof other === 'string') { + } else if (typeof other === "string") { otherPublicKey = new PublicKey(other); } else { - throw new Error('Invalid type for calculateSharedSecret comparison'); + throw new Error("Invalid type for calculateSharedSecret comparison"); } return x25519.getSharedSecret(this.secretKey, otherPublicKey.toBytes()); } @@ -167,7 +183,7 @@ export class SharedSecret implements ISharedSecret { return plaintext.slice(0, end); } - public encrypt(data: Uint8Array): { hmac: Uint8Array, ciphertext: Uint8Array } { + public encrypt(data: Uint8Array): { hmac: Uint8Array; ciphertext: Uint8Array } { const key = this.secret.slice(0, 16); const cipher = ecb(key, { disablePadding: true }); @@ -210,8 +226,8 @@ export class StaticSecret implements IStaticSecret { private secret: Uint8Array; constructor(secret: Uint8Array | string) { - if (typeof secret === 'string') { - secret = hexToBytes(secret, STATIC_SECRET_SIZE); + if (typeof secret === "string") { + secret = hexToBytes(secret); } if (secret.length !== STATIC_SECRET_SIZE) { throw new Error(`Invalid static secret length: expected ${STATIC_SECRET_SIZE} bytes, got ${secret.length}`); diff --git a/src/identity.ts b/src/identity.ts index 8cea026..294d24d 100644 --- a/src/identity.ts +++ b/src/identity.ts @@ -1,9 +1,8 @@ -import { x25519 } from "@noble/curves/ed25519"; -import { PrivateKey, PublicKey, SharedSecret, StaticSecret } from "./crypto"; +import { PrivateKey, PublicKey, SharedSecret } from "./crypto"; import { IPublicKey } from "./crypto.types"; import { NodeHash, IIdentity, ILocalIdentity } from "./identity.types"; import { DecryptedGroupData, DecryptedGroupText } from "./packet.types"; -import { equalBytes, hexToBytes, BufferReader, BufferWriter } from "./parser"; +import { hexToBytes, Reader, Writer } from "@hamradio/packet"; export const parseNodeHash = (hash: NodeHash | string | Uint8Array): NodeHash => { if (hash instanceof Uint8Array) { @@ -22,7 +21,7 @@ export const parseNodeHash = (hash: NodeHash | string | Uint8Array): NodeHash => return parsed[0] as NodeHash; } throw new Error("Invalid NodeHash type"); -} +}; const toPublicKeyBytes = (key: Identity | PublicKey | Uint8Array | string): Uint8Array => { if (key instanceof Identity) { @@ -31,12 +30,12 @@ const toPublicKeyBytes = (key: Identity | PublicKey | Uint8Array | string): Uint return key.toBytes(); } else if (key instanceof Uint8Array) { return key; - } else if (typeof key === 'string') { + } else if (typeof key === "string") { return hexToBytes(key); } else { - throw new Error('Invalid type for toPublicKeyBytes'); + throw new Error("Invalid type for toPublicKeyBytes"); } -} +}; export class Identity implements IIdentity { public publicKey: PublicKey; @@ -44,10 +43,10 @@ export class Identity implements IIdentity { constructor(publicKey: PublicKey | Uint8Array | string) { if (publicKey instanceof PublicKey) { this.publicKey = publicKey; - } else if (publicKey instanceof Uint8Array || typeof publicKey === 'string') { + } else if (publicKey instanceof Uint8Array || typeof publicKey === "string") { this.publicKey = new PublicKey(publicKey); } else { - throw new Error('Invalid type for Identity constructor'); + throw new Error("Invalid type for Identity constructor"); } } @@ -93,16 +92,16 @@ export class LocalIdentity extends Identity implements ILocalIdentity { let otherPublicKey: PublicKey; if (other instanceof Identity) { otherPublicKey = other.publicKey; - } else if ('toBytes' in other) { + } else if ("toBytes" in other) { otherPublicKey = new PublicKey(other.toBytes()); - } else if ('publicKey' in other && other.publicKey instanceof Uint8Array) { + } else if ("publicKey" in other && other.publicKey instanceof Uint8Array) { otherPublicKey = new PublicKey(other.publicKey); - } else if ('publicKey' in other && other.publicKey instanceof PublicKey) { + } else if ("publicKey" in other && other.publicKey instanceof PublicKey) { otherPublicKey = other.publicKey; - } else if ('publicKey' in other && typeof other.publicKey === 'function') { + } else if ("publicKey" in other && typeof other.publicKey === "function") { otherPublicKey = new PublicKey(other.publicKey().toBytes()); } else { - throw new Error('Invalid type for calculateSharedSecret comparison'); + throw new Error("Invalid type for calculateSharedSecret comparison"); } return new SharedSecret(this.privateKey.calculateSharedSecret(otherPublicKey)); } @@ -118,10 +117,10 @@ export class Contact { this.identity = identity; } else if (identity instanceof PublicKey) { this.identity = new Identity(identity); - } else if (identity instanceof Uint8Array || typeof identity === 'string') { + } else if (identity instanceof Uint8Array || typeof identity === "string") { this.identity = new Identity(identity); } else { - throw new Error('Invalid type for Contact constructor'); + throw new Error("Invalid type for Contact constructor"); } } @@ -161,26 +160,26 @@ export class Group { throw new Error("Invalid ciphertext"); } - const reader = new BufferReader(data); - const timestamp = reader.readTimestamp(); - const flags = reader.readByte(); - const textType = (flags >> 2) & 0x3F; + const reader = new Reader(data); + const timestamp = reader.date32(); + const flags = reader.uint8(); + const textType = (flags >> 2) & 0x3f; const attempt = flags & 0x03; - const message = new TextDecoder('utf-8').decode(reader.readBytes()); + const message = new TextDecoder("utf-8").decode(reader.bytes()); return { timestamp, textType, attempt, message - } + }; } - public encryptText(plain: DecryptedGroupText): { hmac: Uint8Array, ciphertext: Uint8Array } { - const writer = new BufferWriter(); - writer.writeTimestamp(plain.timestamp); - const flags = ((plain.textType & 0x3F) << 2) | (plain.attempt & 0x03); - writer.writeByte(flags); - writer.writeBytes(new TextEncoder().encode(plain.message)); + public encryptText(plain: DecryptedGroupText): { hmac: Uint8Array; ciphertext: Uint8Array } { + const writer = new Writer(4 + 1 + new TextEncoder().encode(plain.message).length); + writer.date32(plain.timestamp); + const flags = ((plain.textType & 0x3f) << 2) | (plain.attempt & 0x03); + writer.uint8(flags); + writer.utf8String(plain.message); const data = writer.toBytes(); return this.secret.encrypt(data); } @@ -191,24 +190,24 @@ export class Group { throw new Error("Invalid ciphertext"); } - const reader = new BufferReader(data); + const reader = new Reader(data); return { - timestamp: reader.readTimestamp(), - data: reader.readBytes(reader.remainingBytes()) - } + timestamp: reader.date32(), + data: reader.bytes() + }; } - public encryptData(plain: DecryptedGroupData): { hmac: Uint8Array, ciphertext: Uint8Array } { - const writer = new BufferWriter(); - writer.writeTimestamp(plain.timestamp); - writer.writeBytes(plain.data); + public encryptData(plain: DecryptedGroupData): { hmac: Uint8Array; ciphertext: Uint8Array } { + const writer = new Writer(4 + plain.data.length); + writer.date32(plain.timestamp); + writer.bytes(plain.data); const data = writer.toBytes(); return this.secret.encrypt(data); } } interface CachedLocalIdentity { - identity: LocalIdentity; + identity: LocalIdentity; sharedSecrets: Record; } @@ -229,10 +228,15 @@ export class Contacts { this.contacts[hash].push(contact); } - public decrypt(src: NodeHash | PublicKey, dst: NodeHash, hmac: Uint8Array, ciphertext: Uint8Array): { - localIdentity: LocalIdentity, - contact: Contact, - decrypted: Uint8Array, + public decrypt( + src: NodeHash | PublicKey, + dst: NodeHash, + hmac: Uint8Array, + ciphertext: Uint8Array + ): { + localIdentity: LocalIdentity; + contact: Contact; + decrypted: Uint8Array; } { // Find the public key associated with the source hash. let contacts: Contact[] = []; @@ -259,7 +263,7 @@ export class Contacts { // Find the local identity associated with the destination hash. const dstHash = parseNodeHash(dst) as number; - const localIdentities = this.localIdentities.filter(li => li.identity.publicKey.key[0] === dstHash); + const localIdentities = this.localIdentities.filter((li) => li.identity.publicKey.key[0] === dstHash); if (localIdentities.length === 0) { throw new Error("Unknown destination hash"); } @@ -299,9 +303,13 @@ export class Contacts { this.groups[hash].push(group); } - public decryptGroupText(channelHash: NodeHash, hmac: Uint8Array, ciphertext: Uint8Array): { - decrypted: DecryptedGroupText, - group: Group + public decryptGroupText( + channelHash: NodeHash, + hmac: Uint8Array, + ciphertext: Uint8Array + ): { + decrypted: DecryptedGroupText; + group: Group; } { const hash = parseNodeHash(channelHash) as number; const groups = this.groups[hash] || []; @@ -319,9 +327,13 @@ export class Contacts { throw new Error("Decryption failed with all known groups"); } - public decryptGroupData(channelHash: NodeHash, hmac: Uint8Array, ciphertext: Uint8Array): { - decrypted: DecryptedGroupData, - group: Group + public decryptGroupData( + channelHash: NodeHash, + hmac: Uint8Array, + ciphertext: Uint8Array + ): { + decrypted: DecryptedGroupData; + group: Group; } { const hash = parseNodeHash(channelHash) as number; const groups = this.groups[hash] || []; diff --git a/src/packet.ts b/src/packet.ts index f59b5cd..d3bab4c 100644 --- a/src/packet.ts +++ b/src/packet.ts @@ -17,42 +17,42 @@ import { RouteType, TextPayload, TracePayload, - type IPacket, + type IPacket } from "./packet.types"; -import { NodeHash } from "./identity.types"; -import { - base64ToBytes, - BufferReader, - bytesToHex -} from "./parser"; -import { FieldType, PacketSegment, PacketStructure } from "./parser.types"; +import { base64ToBytes, bytesToHex, FieldType, Dissected, Segment, Reader } from "@hamradio/packet"; export class Packet implements IPacket { // Raw packet bytes. - public header: number; + public header: number; public pathLength: number; - public path: Uint8Array; - public payload: Uint8Array; + public path: Uint8Array; + public payload: Uint8Array; // Parsed packet fields. - public transport?: [number, number]; - public routeType: RouteType; + public transport?: [number, number]; + public routeType: RouteType; public payloadVersion: number; - public payloadType: PayloadType; - public pathHashCount: number; - public pathHashSize: number; - public pathHashBytes: number; - public pathHashes: string[]; + public payloadType: PayloadType; + public pathHashCount: number; + public pathHashSize: number; + public pathHashBytes: number; + public pathHashes: string[]; // Parsed packet segments. - public structure?: PacketStructure | undefined; + public structure?: Dissected | undefined; - constructor(header: number, transport: [number, number] | undefined, pathLength: number, path: Uint8Array, payload: Uint8Array) { + constructor( + header: number, + transport: [number, number] | undefined, + pathLength: number, + path: Uint8Array, + payload: Uint8Array + ) { this.header = header; this.transport = transport; this.pathLength = pathLength; this.path = path; this.payload = payload; - this.routeType = (header) & 0x03; + this.routeType = header & 0x03; this.payloadVersion = (header >> 6) & 0x03; this.payloadType = (header >> 2) & 0x0f; @@ -108,11 +108,17 @@ export class Packet implements IPacket { return; } - let pathHashType: FieldType + let pathHashType: FieldType; switch (this.pathHashSize) { - case 1: pathHashType = FieldType.BYTES; break; - case 2: pathHashType = FieldType.WORDS; break; - case 4: pathHashType = FieldType.DWORDS; break; + case 1: + pathHashType = FieldType.BYTES; + break; + case 2: + pathHashType = FieldType.WORDS; + break; + case 4: + pathHashType = FieldType.DWORDS; + break; default: throw new Error(`Unsupported path hash size: ${this.pathHashSize}`); } @@ -130,35 +136,41 @@ export class Packet implements IPacket { size: 1, bits: [ { name: "payload version", size: 2 }, - { name: "payload type", size: 4 }, - { name: "route type", size: 2 }, + { name: "payload type", size: 4 }, + { name: "route type", size: 2 } ] - }, + } ] }, /* Transport codes */ - ...(Packet.hasTransportCodes(this.routeType) ? [{ - name: "transport codes", - data: new Uint8Array([ - (this.transport![0] >> 8) & 0xff, this.transport![0] & 0xff, - (this.transport![1] >> 8) & 0xff, this.transport![1] & 0xff - ]), - fields: [ - { - name: "transport code 1", - type: FieldType.UINT16_BE, - size: 2, - value: this.transport![0] - }, - { - name: "transport code 2", - type: FieldType.UINT16_BE, - size: 2, - value: this.transport![1] - }, - ] - }] : []), + ...(Packet.hasTransportCodes(this.routeType) + ? [ + { + name: "transport codes", + data: new Uint8Array([ + (this.transport![0] >> 8) & 0xff, + this.transport![0] & 0xff, + (this.transport![1] >> 8) & 0xff, + this.transport![1] & 0xff + ]), + fields: [ + { + name: "transport code 1", + type: FieldType.UINT16_BE, + size: 2, + value: this.transport![0] + }, + { + name: "transport code 2", + type: FieldType.UINT16_BE, + size: 2, + value: this.transport![1] + } + ] + } + ] + : []), /* Path length and hashes */ { @@ -170,8 +182,8 @@ export class Packet implements IPacket { type: FieldType.UINT8, size: 1, bits: [ - { name: "path hash size", size: 2 }, - { name: "path hash count", size: 6 }, + { name: "path hash size", size: 2 }, + { name: "path hash count", size: 6 } ] }, { @@ -180,12 +192,12 @@ export class Packet implements IPacket { size: this.path.length } ] - }, - ] + } + ]; } - public decode(withStructure?: boolean): Payload | { payload: Payload, structure: PacketStructure } { - let result: Payload | { payload: Payload, segment: PacketSegment }; + public decode(withStructure?: boolean): Payload | { payload: Payload; structure: Dissected } { + let result: Payload | { payload: Payload; segment: Segment }; switch (this.payloadType) { case PayloadType.REQUEST: @@ -227,33 +239,33 @@ export class Packet implements IPacket { if (typeof withStructure === "boolean" && withStructure && "segment" in result && "payload" in result) { this.ensureStructure(); - const structure = [ ...this.structure!, result.segment ]; + const structure = [...this.structure!, result.segment]; return { payload: result.payload, structure }; } return result as Payload; } - private decodeEncryptedPayload(reader: BufferReader): EncryptedPayload { - const cipherMAC = reader.readBytes(2); - const cipherText = reader.readBytes(reader.remainingBytes()); + private decodeEncryptedPayload(reader: Reader): EncryptedPayload { + const cipherMAC = reader.bytes(2); + const cipherText = reader.bytes(); return { cipherMAC, cipherText }; } - private decodeRequest(withSegment?: boolean): Payload | { payload: Payload, segment: PacketSegment } { + private decodeRequest(withSegment?: boolean): Payload | { payload: Payload; segment: Segment } { if (this.payload.length < 4) { throw new Error("Invalid request payload: too short"); } - const reader = new BufferReader(this.payload); - const dst = reader.readByte(); - const src = reader.readByte(); + const reader = Reader.fromBytes(this.payload); + const dst = reader.uint8(); + const src = reader.uint8(); const encrypted = this.decodeEncryptedPayload(reader); const payload: RequestPayload = { - type: PayloadType.REQUEST, + type: PayloadType.REQUEST, dst, src, - encrypted, + encrypted }; if (typeof withSegment === "boolean" && withSegment) { @@ -262,30 +274,30 @@ export class Packet implements IPacket { data: this.payload, fields: [ { name: "destination hash", type: FieldType.UINT8, size: 1, value: dst }, - { name: "source hash", type: FieldType.UINT8, size: 1, value: src }, - { name: "cipher MAC", type: FieldType.BYTES, size: 2, value: encrypted.cipherMAC }, - { name: "cipher text", type: FieldType.BYTES, size: encrypted.cipherText.length, value: encrypted.cipherText } + { name: "source hash", type: FieldType.UINT8, size: 1, value: src }, + { name: "cipher MAC", type: FieldType.BYTES, size: 2, value: encrypted.cipherMAC }, + { name: "cipher text", type: FieldType.BYTES, size: encrypted.cipherText.length, value: encrypted.cipherText } ] - } + }; return { payload, segment }; } return payload; } - private decodeResponse(withSegment?: boolean): Payload | { payload: Payload, segment: PacketSegment } { + private decodeResponse(withSegment?: boolean): Payload | { payload: Payload; segment: Segment } { if (this.payload.length < 4) { throw new Error("Invalid response payload: too short"); } - const reader = new BufferReader(this.payload); - const dst = reader.readByte(); - const src = reader.readByte(); + const reader = Reader.fromBytes(this.payload); + const dst = reader.uint8(); + const src = reader.uint8(); const encrypted = this.decodeEncryptedPayload(reader); const payload: ResponsePayload = { - type: PayloadType.RESPONSE, + type: PayloadType.RESPONSE, dst, src, - encrypted, + encrypted }; if (typeof withSegment === "boolean" && withSegment) { @@ -294,9 +306,9 @@ export class Packet implements IPacket { data: this.payload, fields: [ { name: "destination hash", type: FieldType.UINT8, size: 1, value: dst }, - { name: "source hash", type: FieldType.UINT8, size: 1, value: src }, - { name: "cipher MAC", type: FieldType.BYTES, size: 2, value: encrypted.cipherMAC }, - { name: "cipher text", type: FieldType.BYTES, size: encrypted.cipherText.length, value: encrypted.cipherText } + { name: "source hash", type: FieldType.UINT8, size: 1, value: src }, + { name: "cipher MAC", type: FieldType.BYTES, size: 2, value: encrypted.cipherMAC }, + { name: "cipher text", type: FieldType.BYTES, size: encrypted.cipherText.length, value: encrypted.cipherText } ] }; return { payload, segment }; @@ -304,20 +316,20 @@ export class Packet implements IPacket { return payload; } - private decodeText(withSegment?: boolean): Payload | { payload: Payload, segment: PacketSegment } { + private decodeText(withSegment?: boolean): Payload | { payload: Payload; segment: Segment } { if (this.payload.length < 4) { throw new Error("Invalid text payload: too short"); } - const reader = new BufferReader(this.payload); - const dst = reader.readByte(); - const src = reader.readByte(); + const reader = Reader.fromBytes(this.payload); + const dst = reader.uint8(); + const src = reader.uint8(); const encrypted = this.decodeEncryptedPayload(reader); const payload: TextPayload = { type: PayloadType.TEXT, dst, src, - encrypted, + encrypted }; if (typeof withSegment === "boolean" && withSegment) { @@ -326,9 +338,9 @@ export class Packet implements IPacket { data: this.payload, fields: [ { name: "destination hash", type: FieldType.UINT8, size: 1, value: dst }, - { name: "source hash", type: FieldType.UINT8, size: 1, value: src }, - { name: "cipher MAC", type: FieldType.BYTES, size: 2, value: encrypted.cipherMAC }, - { name: "cipher text", type: FieldType.BYTES, size: encrypted.cipherText.length, value: encrypted.cipherText } + { name: "source hash", type: FieldType.UINT8, size: 1, value: src }, + { name: "cipher MAC", type: FieldType.BYTES, size: 2, value: encrypted.cipherMAC }, + { name: "cipher text", type: FieldType.BYTES, size: encrypted.cipherText.length, value: encrypted.cipherText } ] }; return { payload, segment }; @@ -336,78 +348,82 @@ export class Packet implements IPacket { return payload; } - private decodeAck(withSegment?: boolean): Payload | { payload: AckPayload, segment: PacketSegment } { + private decodeAck(withSegment?: boolean): Payload | { payload: AckPayload; segment: Segment } { if (this.payload.length < 4) { throw new Error("Invalid ack payload: too short"); } - const reader = new BufferReader(this.payload); - const checksum = reader.readBytes(4); + const reader = Reader.fromBytes(this.payload); + const checksum = reader.bytes(4); const payload: AckPayload = { - type: PayloadType.ACK, - checksum, + type: PayloadType.ACK, + checksum }; if (typeof withSegment === "boolean" && withSegment) { const segment = { name: "ack payload", data: this.payload, - fields: [ - { name: "checksum", type: FieldType.BYTES, size: 4, value: checksum } - ] + fields: [{ name: "checksum", type: FieldType.BYTES, size: 4, value: checksum }] }; return { payload, segment }; } return payload; } - private decodeAdvert(withSegment?: boolean): Payload | { payload: AdvertPayload, segment: PacketSegment } { + private decodeAdvert(withSegment?: boolean): Payload | { payload: AdvertPayload; segment: Segment } { if (this.payload.length < 4) { throw new Error("Invalid advert payload: too short"); } - const reader = new BufferReader(this.payload); + const reader = Reader.fromBytes(this.payload); const payload: Partial = { - type: PayloadType.ADVERT, - publicKey: reader.readBytes(32), - timestamp: reader.readTimestamp(), - signature: reader.readBytes(64), - } + type: PayloadType.ADVERT, + publicKey: reader.bytes(32), + timestamp: reader.date32(), + signature: reader.bytes(64) + }; - let segment: PacketSegment | undefined; + let segment: Segment | undefined; if (typeof withSegment === "boolean" && withSegment) { segment = { name: "advert payload", data: this.payload, fields: [ - { type: FieldType.BYTES, name: "public key", size: 32 }, - { type: FieldType.UINT32_LE, name: "timestamp", size: 4, value: payload.timestamp! }, - { type: FieldType.BYTES, name: "signature", size: 64 }, + { type: FieldType.BYTES, name: "public key", size: 32 }, + { type: FieldType.UINT32_LE, name: "timestamp", size: 4, value: payload.timestamp! }, + { type: FieldType.BYTES, name: "signature", size: 64 } ] }; } - const flags = reader.readByte(); + const flags = reader.uint8(); const appdata: AdvertAppData = { - nodeType: flags & 0x0f, + nodeType: flags & 0x0f, hasLocation: (flags & AdvertFlag.HAS_LOCATION) !== 0, hasFeature1: (flags & AdvertFlag.HAS_FEATURE1) !== 0, hasFeature2: (flags & AdvertFlag.HAS_FEATURE2) !== 0, - hasName: (flags & AdvertFlag.HAS_NAME) !== 0, - } + hasName: (flags & AdvertFlag.HAS_NAME) !== 0 + }; if (typeof withSegment === "boolean" && withSegment) { - segment!.fields.push({ type: FieldType.BITS, name: "flags", size: 1, value: flags, bits: [ - { size: 1, name: "name flag" }, - { size: 1, name: "feature2 flag" }, - { size: 1, name: "feature1 flag" }, - { size: 1, name: "location flag" }, - { size: 4, name: "node type" }, - ]}); + segment!.fields.push({ + type: FieldType.BITS, + name: "flags", + size: 1, + value: flags, + bits: [ + { size: 1, name: "name flag" }, + { size: 1, name: "feature2 flag" }, + { size: 1, name: "feature1 flag" }, + { size: 1, name: "location flag" }, + { size: 4, name: "node type" } + ] + }); } if (appdata.hasLocation) { - const lat = reader.readInt32LE() / 1000000; - const lon = reader.readInt32LE() / 1000000; + const lat = reader.int32() / 1000000; + const lon = reader.int32() / 1000000; appdata.location = [lat, lon]; if (typeof withSegment === "boolean" && withSegment) { segment!.fields.push({ type: FieldType.UINT32_LE, name: "latitude", size: 4, value: lat }); @@ -415,26 +431,26 @@ export class Packet implements IPacket { } } if (appdata.hasFeature1) { - appdata.feature1 = reader.readUint16LE(); + appdata.feature1 = reader.uint16(); if (typeof withSegment === "boolean" && withSegment) { segment!.fields.push({ type: FieldType.UINT16_LE, name: "feature1", size: 2, value: appdata.feature1 }); } } if (appdata.hasFeature2) { - appdata.feature2 = reader.readUint16LE(); + appdata.feature2 = reader.uint16(); if (typeof withSegment === "boolean" && withSegment) { segment!.fields.push({ type: FieldType.UINT16_LE, name: "feature2", size: 2, value: appdata.feature2 }); } } if (appdata.hasName) { - const nameBytes = reader.readBytes(); - let nullPos = nameBytes.indexOf(0); - if (nullPos === -1) { - nullPos = nameBytes.length; - } - appdata.name = new TextDecoder('utf-8').decode(nameBytes.subarray(0, nullPos)); + appdata.name = reader.cString(); if (typeof withSegment === "boolean" && withSegment) { - segment!.fields.push({ type: FieldType.C_STRING, name: "name", size: nameBytes.length, value: appdata.name }); + segment!.fields.push({ + type: FieldType.C_STRING, + name: "name", + size: appdata.name.length, + value: appdata.name + }); } } @@ -445,18 +461,18 @@ export class Packet implements IPacket { return { ...payload, appdata } as AdvertPayload; } - private decodeGroupText(withSegment?: boolean): Payload | { payload: Payload, segment: PacketSegment } { + private decodeGroupText(withSegment?: boolean): Payload | { payload: Payload; segment: Segment } { if (this.payload.length < 3) { throw new Error("Invalid group text payload: too short"); } - const reader = new BufferReader(this.payload); - const channelHash = reader.readByte(); + const reader = Reader.fromBytes(this.payload); + const channelHash = reader.uint8(); const encrypted = this.decodeEncryptedPayload(reader); const payload: GroupTextPayload = { - type: PayloadType.GROUP_TEXT, + type: PayloadType.GROUP_TEXT, channelHash, - encrypted, + encrypted }; if (typeof withSegment === "boolean" && withSegment) { @@ -465,8 +481,8 @@ export class Packet implements IPacket { data: this.payload, fields: [ { name: "channel hash", type: FieldType.UINT8, size: 1, value: channelHash }, - { name: "cipher MAC", type: FieldType.BYTES, size: 2, value: encrypted.cipherMAC }, - { name: "cipher text", type: FieldType.BYTES, size: encrypted.cipherText.length, value: encrypted.cipherText } + { name: "cipher MAC", type: FieldType.BYTES, size: 2, value: encrypted.cipherMAC }, + { name: "cipher text", type: FieldType.BYTES, size: encrypted.cipherText.length, value: encrypted.cipherText } ] }; return { payload, segment }; @@ -474,16 +490,16 @@ export class Packet implements IPacket { return payload; } - private decodeGroupData(withSegment?: boolean): Payload | { payload: Payload, segment: PacketSegment } { + private decodeGroupData(withSegment?: boolean): Payload | { payload: Payload; segment: Segment } { if (this.payload.length < 3) { throw new Error("Invalid group data payload: too short"); } - const reader = new BufferReader(this.payload); + const reader = Reader.fromBytes(this.payload); const payload: GroupDataPayload = { - type: PayloadType.GROUP_DATA, - channelHash: reader.readByte(), - encrypted: this.decodeEncryptedPayload(reader), + type: PayloadType.GROUP_DATA, + channelHash: reader.uint8(), + encrypted: this.decodeEncryptedPayload(reader) }; if (typeof withSegment === "boolean" && withSegment) { @@ -492,8 +508,13 @@ export class Packet implements IPacket { data: this.payload, fields: [ { name: "channel hash", type: FieldType.UINT8, size: 1, value: payload.channelHash }, - { name: "cipher MAC", type: FieldType.BYTES, size: 2, value: payload.encrypted.cipherMAC }, - { name: "cipher text", type: FieldType.BYTES, size: payload.encrypted.cipherText.length, value: payload.encrypted.cipherText } + { name: "cipher MAC", type: FieldType.BYTES, size: 2, value: payload.encrypted.cipherMAC }, + { + name: "cipher text", + type: FieldType.BYTES, + size: payload.encrypted.cipherText.length, + value: payload.encrypted.cipherText + } ] }; return { payload, segment }; @@ -501,18 +522,18 @@ export class Packet implements IPacket { return payload; } - private decodeAnonReq(withSegment?: boolean): Payload | { payload: Payload, segment: PacketSegment } { + private decodeAnonReq(withSegment?: boolean): Payload | { payload: Payload; segment: Segment } { if (this.payload.length < 1 + 32 + 2) { throw new Error("Invalid anon req payload: too short"); } - const reader = new BufferReader(this.payload); + const reader = Reader.fromBytes(this.payload); const payload: AnonReqPayload = { - type: PayloadType.ANON_REQ, - dst: reader.readByte(), - publicKey: reader.readBytes(32), - encrypted: this.decodeEncryptedPayload(reader), - } + type: PayloadType.ANON_REQ, + dst: reader.uint8(), + publicKey: reader.bytes(32), + encrypted: this.decodeEncryptedPayload(reader) + }; if (typeof withSegment === "boolean" && withSegment) { const segment = { @@ -520,9 +541,14 @@ export class Packet implements IPacket { data: this.payload, fields: [ { name: "destination hash", type: FieldType.UINT8, size: 1, value: payload.dst }, - { name: "public key", type: FieldType.BYTES, size: 32, value: payload.publicKey }, - { name: "cipher MAC", type: FieldType.BYTES, size: 2, value: payload.encrypted.cipherMAC }, - { name: "cipher text", type: FieldType.BYTES, size: payload.encrypted.cipherText.length, value: payload.encrypted.cipherText } + { name: "public key", type: FieldType.BYTES, size: 32, value: payload.publicKey }, + { name: "cipher MAC", type: FieldType.BYTES, size: 2, value: payload.encrypted.cipherMAC }, + { + name: "cipher text", + type: FieldType.BYTES, + size: payload.encrypted.cipherText.length, + value: payload.encrypted.cipherText + } ] }; return { payload, segment }; @@ -530,16 +556,16 @@ export class Packet implements IPacket { return payload; } - private decodePath(withSegment?: boolean): Payload | { payload: Payload, segment: PacketSegment } { + private decodePath(withSegment?: boolean): Payload | { payload: Payload; segment: Segment } { if (this.payload.length < 2) { throw new Error("Invalid path payload: too short"); } - const reader = new BufferReader(this.payload); + const reader = Reader.fromBytes(this.payload); const payload: PathPayload = { type: PayloadType.PATH, - dst: reader.readByte(), - src: reader.readByte(), + dst: reader.uint8(), + src: reader.uint8() }; if (typeof withSegment === "boolean" && withSegment) { @@ -548,7 +574,7 @@ export class Packet implements IPacket { data: this.payload, fields: [ { name: "destination hash", type: FieldType.UINT8, size: 1, value: payload.dst }, - { name: "source hash", type: FieldType.UINT8, size: 1, value: payload.src } + { name: "source hash", type: FieldType.UINT8, size: 1, value: payload.src } ] }; return { payload, segment }; @@ -556,18 +582,18 @@ export class Packet implements IPacket { return payload; } - private decodeTrace(withSegment?: boolean): Payload | { payload: Payload, segment: PacketSegment } { + private decodeTrace(withSegment?: boolean): Payload | { payload: Payload; segment: Segment } { if (this.payload.length < 9) { throw new Error("Invalid trace payload: too short"); } - const reader = new BufferReader(this.payload); + const reader = Reader.fromBytes(this.payload); const payload: TracePayload = { - type: PayloadType.TRACE, - tag: reader.readUint32LE() >>> 0, - authCode: reader.readUint32LE() >>> 0, - flags: reader.readByte() & 0x03, - nodes: reader.readBytes() + type: PayloadType.TRACE, + tag: reader.uint32(), + authCode: reader.uint32(), + flags: reader.uint8() & 0x03, + nodes: reader.bytes() }; if (typeof withSegment === "boolean" && withSegment) { @@ -575,10 +601,10 @@ export class Packet implements IPacket { name: "trace payload", data: this.payload, fields: [ - { name: "tag", type: FieldType.DWORDS, size: 4, value: payload.tag }, + { name: "tag", type: FieldType.DWORDS, size: 4, value: payload.tag }, { name: "auth code", type: FieldType.DWORDS, size: 4, value: payload.authCode }, - { name: "flags", type: FieldType.UINT8, size: 1, value: payload.flags }, - { name: "nodes", type: FieldType.BYTES, size: payload.nodes.length, value: payload.nodes } + { name: "flags", type: FieldType.UINT8, size: 1, value: payload.flags }, + { name: "nodes", type: FieldType.BYTES, size: payload.nodes.length, value: payload.nodes } ] }; return { payload, segment }; @@ -586,19 +612,17 @@ export class Packet implements IPacket { return payload; } - private decodeRawCustom(withSegment?: boolean): Payload | { payload: Payload, segment: PacketSegment } { + private decodeRawCustom(withSegment?: boolean): Payload | { payload: Payload; segment: Segment } { const payload: RawCustomPayload = { type: PayloadType.RAW_CUSTOM, - data: this.payload, + data: this.payload }; if (typeof withSegment === "boolean" && withSegment) { const segment = { name: "raw custom payload", data: this.payload, - fields: [ - { name: "data", type: FieldType.BYTES, size: this.payload.length, value: this.payload } - ] + fields: [{ name: "data", type: FieldType.BYTES, size: this.payload.length, value: this.payload }] }; return { payload, segment }; } diff --git a/src/packet.types.ts b/src/packet.types.ts index 50a3ae9..bbf76a2 100644 --- a/src/packet.types.ts +++ b/src/packet.types.ts @@ -1,5 +1,6 @@ import { NodeHash } from "./identity.types"; -import { PacketStructure } from "./parser.types"; +import { Dissected } from "@hamradio/packet"; +//import { PacketStructure } from "./parser.types"; // IPacket contains the raw packet bytes. export type Uint16 = number; // 0..65535 @@ -7,207 +8,208 @@ export type Uint16 = number; // 0..65535 /* Packet types and structures. */ export interface IPacket { - header: number; + header: number; transport?: [Uint16, Uint16]; pathLength: number; - path: Uint8Array; - payload: Uint8Array; + path: Uint8Array; + payload: Uint8Array; - decode(withStructure?: boolean): Payload | { payload: Payload, structure: PacketStructure } + decode(withStructure?: boolean): Payload | { payload: Payload; structure: Dissected }; } export enum RouteType { - TRANSPORT_FLOOD = 0, - FLOOD = 1, - DIRECT = 2, + TRANSPORT_FLOOD = 0, + FLOOD = 1, + DIRECT = 2, TRANSPORT_DIRECT = 3 } export enum PayloadType { - REQUEST = 0x00, - RESPONSE = 0x01, - TEXT = 0x02, - ACK = 0x03, - ADVERT = 0x04, + REQUEST = 0x00, + RESPONSE = 0x01, + TEXT = 0x02, + ACK = 0x03, + ADVERT = 0x04, GROUP_TEXT = 0x05, GROUP_DATA = 0x06, - ANON_REQ = 0x07, - PATH = 0x08, - TRACE = 0x09, - RAW_CUSTOM = 0x0f, + ANON_REQ = 0x07, + PATH = 0x08, + TRACE = 0x09, + RAW_CUSTOM = 0x0f } -export type Payload = BasePayload & ( - | RequestPayload - | ResponsePayload - | TextPayload - | AckPayload - | AdvertPayload - | GroupTextPayload - | GroupDataPayload - | AnonReqPayload - | PathPayload - | TracePayload - | RawCustomPayload -); +export type Payload = BasePayload & + ( + | RequestPayload + | ResponsePayload + | TextPayload + | AckPayload + | AdvertPayload + | GroupTextPayload + | GroupDataPayload + | AnonReqPayload + | PathPayload + | TracePayload + | RawCustomPayload + ); export interface BasePayload { type: PayloadType; } export interface EncryptedPayload { - cipherMAC: Uint8Array; + cipherMAC: Uint8Array; cipherText: Uint8Array; } export interface RequestPayload extends BasePayload { - type: PayloadType.REQUEST; - dst: NodeHash; - src: NodeHash; - encrypted: EncryptedPayload; + type: PayloadType.REQUEST; + dst: NodeHash; + src: NodeHash; + encrypted: EncryptedPayload; decrypted?: DecryptedRequest; } export enum RequestType { - GET_STATS = 0x01, - KEEP_ALIVE = 0x02, - GET_TELEMETRY = 0x03, + GET_STATS = 0x01, + KEEP_ALIVE = 0x02, + GET_TELEMETRY = 0x03, GET_MIN_MAX_AVG = 0x04, - GET_ACL = 0x05, - GET_NEIGHBORS = 0x06, - GET_OWNER_INFO = 0x07, + GET_ACL = 0x05, + GET_NEIGHBORS = 0x06, + GET_OWNER_INFO = 0x07 } export interface DecryptedRequest { - timestamp: Date; + timestamp: Date; requestType: RequestType; requestData: Uint8Array; } export interface ResponsePayload extends BasePayload { - type: PayloadType.RESPONSE; - dst: NodeHash; - src: NodeHash; - encrypted: EncryptedPayload; + type: PayloadType.RESPONSE; + dst: NodeHash; + src: NodeHash; + encrypted: EncryptedPayload; decrypted?: DecryptedResponse; } export interface DecryptedResponse { - timestamp: Date; + timestamp: Date; responseData: Uint8Array; } export interface TextPayload extends BasePayload { - type: PayloadType.TEXT; - dst: NodeHash; - src: NodeHash; - encrypted: EncryptedPayload; + type: PayloadType.TEXT; + dst: NodeHash; + src: NodeHash; + encrypted: EncryptedPayload; decrypted?: DecryptedText; } export enum TextType { - PLAIN_TEXT = 0x00, - CLI_COMMAND = 0x01, - SIGNED_PLAIN_TEXT = 0x02, + PLAIN_TEXT = 0x00, + CLI_COMMAND = 0x01, + SIGNED_PLAIN_TEXT = 0x02 } export interface DecryptedText { timestamp: Date; - textType: TextType; - attempt: number; - message: string; + textType: TextType; + attempt: number; + message: string; } export interface AckPayload extends BasePayload { - type: PayloadType.ACK; - checksum: Uint8Array; + type: PayloadType.ACK; + checksum: Uint8Array; } export interface AdvertPayload extends BasePayload { - type: PayloadType.ADVERT; - publicKey: Uint8Array; - timestamp: Date; - signature: Uint8Array; - appdata: AdvertAppData; + type: PayloadType.ADVERT; + publicKey: Uint8Array; + timestamp: Date; + signature: Uint8Array; + appdata: AdvertAppData; } export enum NodeType { - CHAT_NODE = 0x01, - REPEATER = 0x02, + CHAT_NODE = 0x01, + REPEATER = 0x02, ROOM_SERVER = 0x03, - SENSOR_NODE = 0x04, + SENSOR_NODE = 0x04 } export enum AdvertFlag { HAS_LOCATION = 0x10, HAS_FEATURE1 = 0x20, HAS_FEATURE2 = 0x40, - HAS_NAME = 0x80, + HAS_NAME = 0x80 } export interface AdvertAppData { - nodeType: NodeType; + nodeType: NodeType; hasLocation: boolean; - location?: [number, number]; + location?: [number, number]; hasFeature1: boolean; - feature1?: Uint16; + feature1?: Uint16; hasFeature2: boolean; - feature2?: Uint16; - hasName: boolean; - name?: string; + feature2?: Uint16; + hasName: boolean; + name?: string; } export interface GroupTextPayload extends BasePayload { - type: PayloadType.GROUP_TEXT; + type: PayloadType.GROUP_TEXT; channelHash: NodeHash; - encrypted: EncryptedPayload; - decrypted?: DecryptedGroupText; + encrypted: EncryptedPayload; + decrypted?: DecryptedGroupText; } export interface DecryptedGroupText { timestamp: Date; - textType: TextType; - attempt: number; - message: string; + textType: TextType; + attempt: number; + message: string; } export interface GroupDataPayload extends BasePayload { - type: PayloadType.GROUP_DATA; + type: PayloadType.GROUP_DATA; channelHash: NodeHash; - encrypted: EncryptedPayload; - decrypted?: DecryptedGroupData; + encrypted: EncryptedPayload; + decrypted?: DecryptedGroupData; } export interface DecryptedGroupData { - timestamp: Date; - data: Uint8Array; + timestamp: Date; + data: Uint8Array; } export interface AnonReqPayload extends BasePayload { - type: PayloadType.ANON_REQ; - dst: NodeHash; - publicKey: Uint8Array; - encrypted: EncryptedPayload; + type: PayloadType.ANON_REQ; + dst: NodeHash; + publicKey: Uint8Array; + encrypted: EncryptedPayload; decrypted?: DecryptedAnonReq; } export interface DecryptedAnonReq { timestamp: Date; - data: Uint8Array; + data: Uint8Array; } export interface PathPayload extends BasePayload { type: PayloadType.PATH; - dst: NodeHash; - src: NodeHash; + dst: NodeHash; + src: NodeHash; } export interface TracePayload extends BasePayload { - type: PayloadType.TRACE; - tag: number; + type: PayloadType.TRACE; + tag: number; authCode: number; - flags: number; - nodes: Uint8Array; + flags: number; + nodes: Uint8Array; } export interface RawCustomPayload extends BasePayload { diff --git a/src/parser.ts b/src/parser.ts deleted file mode 100644 index ad85165..0000000 --- a/src/parser.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { equalBytes } from "@noble/ciphers/utils.js"; -import { bytesToHex, hexToBytes as nobleHexToBytes } from "@noble/hashes/utils.js"; - -export { - bytesToHex, - equalBytes -}; - -export const base64ToBytes = (base64: string, size?: number): Uint8Array => { - // Normalize URL-safe base64 to standard base64 - let normalized = base64.replace(/-/g, '+').replace(/_/g, '/'); - // Add padding if missing - while (normalized.length % 4 !== 0) { - normalized += '='; - } - const binaryString = atob(normalized); - const bytes = new Uint8Array(binaryString.length); - for (let i = 0; i < binaryString.length; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - if (size !== undefined && bytes.length !== size) { - throw new Error(`Invalid base64 length: expected ${size} bytes, got ${bytes.length}`); - } - return bytes; -} - -// Note: encodedStringToBytes removed — prefer explicit parsers. -// Use `hexToBytes` for hex inputs and `base64ToBytes` for base64 inputs. - -// Wrapper around @noble/hashes hexToBytes that optionally validates size. -export const hexToBytes = (hex: string, size?: number): Uint8Array => { - const bytes = nobleHexToBytes(hex); - if (size !== undefined && bytes.length !== size) { - throw new Error(`Invalid hex length: expected ${size} bytes, got ${bytes.length}`); - } - return bytes; -}; - -export class BufferReader { - private buffer: Uint8Array; - private offset: number; - - constructor(buffer: Uint8Array) { - this.buffer = buffer; - this.offset = 0; - } - - public readByte(): number { - if (!this.hasMore()) throw new Error('read past end'); - return this.buffer[this.offset++]; - } - - public readBytes(length?: number): Uint8Array { - if (length === undefined) { - length = this.buffer.length - this.offset; - } - if (this.remainingBytes() < length) throw new Error('read past end'); - const bytes = this.buffer.slice(this.offset, this.offset + length); - this.offset += length; - return bytes; -} - - public hasMore(): boolean { - return this.offset < this.buffer.length; - } - - public remainingBytes(): number { - return this.buffer.length - this.offset; - } - - public peekByte(): number { - if (!this.hasMore()) throw new Error('read past end'); - return this.buffer[this.offset]; - } - - public readUint16LE(): number { - if (this.remainingBytes() < 2) throw new Error('read past end'); - const value = this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8); - this.offset += 2; - return value; - } - - public readUint32LE(): number { - if (this.remainingBytes() < 4) throw new Error('read past end'); - const value = (this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8) | (this.buffer[this.offset + 2] << 16) | (this.buffer[this.offset + 3] << 24)) >>> 0; - this.offset += 4; - return value; - } - - public readInt16LE(): number { - if (this.remainingBytes() < 2) throw new Error('read past end'); - const value = this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8); - this.offset += 2; - return value < 0x8000 ? value : value - 0x10000; - } - - public readInt32LE(): number { - if (this.remainingBytes() < 4) throw new Error('read past end'); - const u = (this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8) | (this.buffer[this.offset + 2] << 16) | (this.buffer[this.offset + 3] << 24)) >>> 0; - this.offset += 4; - return u < 0x80000000 ? u : u - 0x100000000; - } - - public readTimestamp(): Date { - const timestamp = this.readUint32LE(); - return new Date(timestamp * 1000); - } -} - -export class BufferWriter { - private buffer: number[] = []; - - public writeByte(value: number): void { - this.buffer.push(value & 0xFF); - } - - public writeBytes(bytes: Uint8Array): void { - this.buffer.push(...bytes); - } - - public writeUint16LE(value: number): void { - this.buffer.push(value & 0xFF, (value >> 8) & 0xFF); - } - - public writeUint32LE(value: number): void { - this.buffer.push( - value & 0xFF, - (value >> 8) & 0xFF, - (value >> 16) & 0xFF, - (value >> 24) & 0xFF - ); - } - - public writeInt16LE(value: number): void { - this.writeUint16LE(value < 0 ? value + 0x10000 : value); - } - - public writeInt32LE(value: number): void { - this.writeUint32LE(value < 0 ? value + 0x100000000 : value); - } - - public writeTimestamp(date: Date): void { - const timestamp = Math.floor(date.getTime() / 1000); - this.writeUint32LE(timestamp); - } - - public toBytes(): Uint8Array { - return new Uint8Array(this.buffer); - } -} diff --git a/src/parser.types.ts b/src/parser.types.ts deleted file mode 100644 index a42043f..0000000 --- a/src/parser.types.ts +++ /dev/null @@ -1,35 +0,0 @@ -export enum FieldType { - BITS = 0, - UINT8 = 1, - UINT16_LE = 2, - UINT16_BE = 3, - UINT32_LE = 4, - UINT32_BE = 5, - BYTES = 6, // 8-bits per value - WORDS = 7, // 16-bits per value - DWORDS = 8, // 32-bits per value - QWORDS = 9, // 64-bits per value - C_STRING = 10, -} - -// Interface for the parsed packet segments, used for debugging and testing. -export type PacketStructure = PacketSegment[]; - -export interface PacketSegment { - name: string; - data: Uint8Array; - fields: PacketField[]; -} - -export interface PacketField { - type: FieldType; - size: number; // Size in bytes - name?: string; - bits?: PacketFieldBit[]; // Only for bit fields in FieldType.BITS - value?: any; // Optional decoded value -} - -export interface PacketFieldBit { - name: string; - size: number; // Size in bits -} diff --git a/test/crypto.test.ts b/test/crypto.test.ts index 0dc5e8f..01ccb1e 100644 --- a/test/crypto.test.ts +++ b/test/crypto.test.ts @@ -1,39 +1,39 @@ -import { describe, it, expect } from 'vitest'; -import { PublicKey, PrivateKey, SharedSecret, StaticSecret } from '../src/crypto'; -import { bytesToHex, hexToBytes } from '../src/parser'; +import { describe, it, expect } from "vitest"; +import { bytesToHex } from "@hamradio/packet"; +import { PublicKey, PrivateKey, SharedSecret, StaticSecret } from "../src/crypto"; const randomBytes = (len: number) => Uint8Array.from({ length: len }, () => Math.floor(Math.random() * 256)); -describe('PublicKey', () => { +describe("PublicKey", () => { const keyBytes = randomBytes(32); const keyHex = bytesToHex(keyBytes); - it('constructs from Uint8Array', () => { + it("constructs from Uint8Array", () => { const pk = new PublicKey(keyBytes); expect(pk.toBytes()).toEqual(keyBytes); }); - it('constructs from string', () => { + it("constructs from string", () => { const pk = new PublicKey(keyHex); expect(pk.toBytes()).toEqual(keyBytes); }); - it('throws on invalid constructor input', () => { - // @ts-expect-error + it("throws on invalid constructor input", () => { + // @ts-expect-error testing invalid input expect(() => new PublicKey(123)).toThrow(); }); - it('toHash returns a NodeHash', () => { + it("toHash returns a NodeHash", () => { const pk = new PublicKey(keyBytes); - expect(typeof pk.toHash()).toBe('number'); + expect(typeof pk.toHash()).toBe("number"); }); - it('toString returns hex', () => { + it("toString returns hex", () => { const pk = new PublicKey(keyBytes); expect(pk.toString()).toBe(keyHex); }); - it('equals works for PublicKey, Uint8Array, and string', () => { + it("equals works for PublicKey, Uint8Array, and string", () => { const pk = new PublicKey(keyBytes); expect(pk.equals(pk)).toBe(true); expect(pk.equals(keyBytes)).toBe(true); @@ -41,41 +41,41 @@ describe('PublicKey', () => { expect(pk.equals(randomBytes(32))).toBe(false); }); - it('throws on equals with invalid type', () => { + it("throws on equals with invalid type", () => { const pk = new PublicKey(keyBytes); - // @ts-expect-error + // @ts-expect-error testing invalid input expect(() => pk.equals(123)).toThrow(); }); - it('verify returns false for invalid signature', () => { + it("verify returns false for invalid signature", () => { const pk = new PublicKey(keyBytes); expect(pk.verify(new Uint8Array([1, 2, 3]), randomBytes(64))).toBe(false); }); - it('throws on verify with wrong signature length', () => { + it("throws on verify with wrong signature length", () => { const pk = new PublicKey(keyBytes); expect(() => pk.verify(new Uint8Array([1, 2, 3]), randomBytes(10))).toThrow(); }); }); -describe('PrivateKey', () => { +describe("PrivateKey", () => { const seed = randomBytes(32); - it('constructs from Uint8Array', () => { + it("constructs from Uint8Array", () => { const sk = new PrivateKey(seed); expect(sk.toPublicKey()).toBeInstanceOf(PublicKey); }); - it('constructs from string', () => { + it("constructs from string", () => { const sk = new PrivateKey(bytesToHex(seed)); expect(sk.toPublicKey()).toBeInstanceOf(PublicKey); }); - it('throws on invalid seed length', () => { + it("throws on invalid seed length", () => { expect(() => new PrivateKey(randomBytes(10))).toThrow(); }); - it('sign and verify', () => { + it("sign and verify", () => { const sk = new PrivateKey(seed); const pk = sk.toPublicKey(); const msg = new Uint8Array([1, 2, 3]); @@ -83,7 +83,7 @@ describe('PrivateKey', () => { expect(pk.verify(msg, sig)).toBe(true); }); - it('calculateSharedSecret returns Uint8Array', () => { + it("calculateSharedSecret returns Uint8Array", () => { const sk1 = new PrivateKey(seed); const sk2 = PrivateKey.generate(); const pk2 = sk2.toPublicKey(); @@ -92,7 +92,7 @@ describe('PrivateKey', () => { expect(secret.length).toBeGreaterThan(0); }); - it('calculateSharedSecret accepts string and Uint8Array', () => { + it("calculateSharedSecret accepts string and Uint8Array", () => { const sk1 = new PrivateKey(seed); const sk2 = PrivateKey.generate(); const pk2 = sk2.toPublicKey(); @@ -100,47 +100,47 @@ describe('PrivateKey', () => { expect(sk1.calculateSharedSecret(pk2.toString())).toBeInstanceOf(Uint8Array); }); - it('throws on calculateSharedSecret with invalid type', () => { + it("throws on calculateSharedSecret with invalid type", () => { const sk = new PrivateKey(seed); - // @ts-expect-error + // @ts-expect-error testing invalid input expect(() => sk.calculateSharedSecret(123)).toThrow(); }); - it('generate returns PrivateKey', () => { + it("generate returns PrivateKey", () => { expect(PrivateKey.generate()).toBeInstanceOf(PrivateKey); }); }); -describe('SharedSecret', () => { +describe("SharedSecret", () => { const secret = randomBytes(32); - it('constructs from 32 bytes', () => { + it("constructs from 32 bytes", () => { const ss = new SharedSecret(secret); expect(ss.toBytes()).toEqual(secret); }); - it('pads if 16 bytes', () => { + it("pads if 16 bytes", () => { const short = randomBytes(16); const ss = new SharedSecret(short); expect(ss.toBytes().length).toBe(32); expect(Array.from(ss.toBytes()).slice(16)).toEqual(Array.from(short)); }); - it('throws on invalid length', () => { + it("throws on invalid length", () => { expect(() => new SharedSecret(randomBytes(10))).toThrow(); }); - it('toHash returns number', () => { + it("toHash returns number", () => { const ss = new SharedSecret(secret); - expect(typeof ss.toHash()).toBe('number'); + expect(typeof ss.toHash()).toBe("number"); }); - it('toString returns hex', () => { + it("toString returns hex", () => { const ss = new SharedSecret(secret); expect(ss.toString()).toBe(bytesToHex(secret)); }); - it('encrypt and decrypt roundtrip', () => { + it("encrypt and decrypt roundtrip", () => { const ss = new SharedSecret(secret); const data = new Uint8Array([1, 2, 3, 4, 5]); const { hmac, ciphertext } = ss.encrypt(data); @@ -148,14 +148,14 @@ describe('SharedSecret', () => { expect(Array.from(decrypted.slice(0, data.length))).toEqual(Array.from(data)); }); - it('throws on decrypt with wrong hmac', () => { + it("throws on decrypt with wrong hmac", () => { const ss = new SharedSecret(secret); const data = new Uint8Array([1, 2, 3]); const { ciphertext } = ss.encrypt(data); expect(() => ss.decrypt(new Uint8Array([0, 0]), ciphertext)).toThrow(); }); - it('throws on decrypt with wrong hmac length', () => { + it("throws on decrypt with wrong hmac length", () => { const ss = new SharedSecret(secret); expect(() => ss.decrypt(new Uint8Array([1]), new Uint8Array([1, 2, 3]))).toThrow(); }); @@ -165,34 +165,34 @@ describe('SharedSecret', () => { expect(ss).toBeInstanceOf(SharedSecret); }); - it('fromName with #group', () => { + it("fromName with #group", () => { const ss = SharedSecret.fromName("#group"); expect(ss).toBeInstanceOf(SharedSecret); }); - it('fromName throws on invalid name', () => { + it("fromName throws on invalid name", () => { expect(() => SharedSecret.fromName("foo")).toThrow(); }); }); -describe('StaticSecret', () => { +describe("StaticSecret", () => { const secret = randomBytes(32); - it('constructs from Uint8Array', () => { + it("constructs from Uint8Array", () => { const ss = new StaticSecret(secret); expect(ss.publicKey()).toBeInstanceOf(PublicKey); }); - it('constructs from string', () => { + it("constructs from string", () => { const ss = new StaticSecret(bytesToHex(secret)); expect(ss.publicKey()).toBeInstanceOf(PublicKey); }); - it('throws on invalid length', () => { + it("throws on invalid length", () => { expect(() => new StaticSecret(randomBytes(10))).toThrow(); }); - it('diffieHellman returns SharedSecret', () => { + it("diffieHellman returns SharedSecret", () => { const ss1 = new StaticSecret(secret); const ss2 = new StaticSecret(randomBytes(32)); const pk2 = ss2.publicKey(); diff --git a/test/identity.test.ts b/test/identity.test.ts index cd585d2..1f94133 100644 --- a/test/identity.test.ts +++ b/test/identity.test.ts @@ -1,33 +1,33 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { Identity, LocalIdentity, Contact, Group, Contacts, parseNodeHash } from '../src/identity'; -import { PrivateKey, PublicKey, SharedSecret } from '../src/crypto'; -import { DecryptedGroupText, DecryptedGroupData } from '../src/packet.types'; -import { bytesToHex } from '../src/parser'; +import { describe, it, expect, beforeEach } from "vitest"; +import { bytesToHex } from "@hamradio/packet"; +import { Identity, LocalIdentity, Contact, Group, Contacts, parseNodeHash } from "../src/identity"; +import { PrivateKey, PublicKey, SharedSecret } from "../src/crypto"; +import { DecryptedGroupText, DecryptedGroupData } from "../src/packet.types"; function randomBytes(len: number) { return Uint8Array.from({ length: len }, () => Math.floor(Math.random() * 256)); } -describe('parseNodeHash', () => { - it('parses Uint8Array', () => { +describe("parseNodeHash", () => { + it("parses Uint8Array", () => { expect(parseNodeHash(Uint8Array.of(0x42))).toBe(0x42); }); - it('parses number', () => { + it("parses number", () => { expect(parseNodeHash(0x42)).toBe(0x42); expect(() => parseNodeHash(-1)).toThrow(); expect(() => parseNodeHash(256)).toThrow(); }); - it('parses string', () => { - expect(parseNodeHash('2a')).toBe(0x2a); - expect(() => parseNodeHash('2a2a')).toThrow(); + it("parses string", () => { + expect(parseNodeHash("2a")).toBe(0x2a); + expect(() => parseNodeHash("2a2a")).toThrow(); }); - it('throws on invalid type', () => { - // @ts-expect-error + it("throws on invalid type", () => { + // @ts-expect-error testing invalid input expect(() => parseNodeHash({})).toThrow(); }); }); -describe('Identity', () => { +describe("Identity", () => { let pub: Uint8Array; let identity: Identity; @@ -36,31 +36,31 @@ describe('Identity', () => { identity = new Identity(pub); }); - it('constructs from Uint8Array', () => { + it("constructs from Uint8Array", () => { expect(identity.publicKey.toBytes()).toEqual(pub); }); - it('constructs from string', () => { + it("constructs from string", () => { const hex = bytesToHex(pub); const id = new Identity(hex); expect(id.publicKey.toBytes()).toEqual(pub); }); - it('hash returns NodeHash', () => { - expect(typeof identity.hash()).toBe('number'); + it("hash returns NodeHash", () => { + expect(typeof identity.hash()).toBe("number"); }); - it('toString returns string', () => { - expect(typeof identity.toString()).toBe('string'); + it("toString returns string", () => { + expect(typeof identity.toString()).toBe("string"); }); - it('verify delegates to publicKey', () => { + it("verify delegates to publicKey", () => { const msg = randomBytes(10); const sig = randomBytes(64); expect(identity.verify(sig, msg)).toBe(identity.publicKey.verify(msg, sig)); }); - it('matches works for various types', () => { + it("matches works for various types", () => { expect(identity.matches(identity)).toBe(true); expect(identity.matches(identity.publicKey)).toBe(true); expect(identity.matches(identity.publicKey.toBytes())).toBe(true); @@ -68,18 +68,20 @@ describe('Identity', () => { expect(identity.matches(randomBytes(32))).toBe(false); }); - it('constructor throws on invalid type', () => { + /* eslint-disable @typescript-eslint/no-explicit-any */ + it("constructor throws on invalid type", () => { expect(() => new (Identity as any)(123)).toThrow(); }); - it('matches throws on invalid type', () => { + it("matches throws on invalid type", () => { const pub = randomBytes(32); const id = new Identity(pub); + // @ts-expect-error testing wrong type expect(() => id.matches({})).toThrow(); }); }); -describe('LocalIdentity', () => { +describe("LocalIdentity", () => { let priv: PrivateKey; let pub: PublicKey; let local: LocalIdentity; @@ -90,45 +92,45 @@ describe('LocalIdentity', () => { local = new LocalIdentity(priv, pub); }); - it('constructs from PrivateKey and PublicKey', () => { + it("constructs from PrivateKey and PublicKey", () => { expect(local.publicKey.toBytes()).toEqual(pub.toBytes()); }); - it('constructs from Uint8Array and string', () => { + it("constructs from Uint8Array and string", () => { const priv2 = PrivateKey.generate(); const pub2 = priv2.toPublicKey(); const local2 = new LocalIdentity(priv2.toBytes(), pub2.toString()); expect(local2.publicKey.toBytes()).toEqual(pub2.toBytes()); }); - it('signs message', () => { - const msg = Uint8Array.from([1,2,3,4,5,6,7,8,9,10,11,12]); + it("signs message", () => { + const msg = Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); const sig = local.sign(msg); expect(sig).toBeInstanceOf(Uint8Array); expect(sig.length).toBeGreaterThan(0); }); - it('calculates shared secret with Identity', () => { + it("calculates shared secret with Identity", () => { const otherPriv = PrivateKey.generate(); const other = new Identity(otherPriv.toPublicKey().toBytes()); const secret = local.calculateSharedSecret(other); expect(secret).toBeInstanceOf(SharedSecret); }); - it('calculates shared secret with IPublicKey', () => { + it("calculates shared secret with IPublicKey", () => { const otherPriv = PrivateKey.generate(); const otherPub = otherPriv.toPublicKey(); const secret = local.calculateSharedSecret(otherPub); expect(secret).toBeInstanceOf(SharedSecret); }); - it('calculateSharedSecret throws on invalid input', () => { + it("calculateSharedSecret throws on invalid input", () => { const other: any = {}; expect(() => local.calculateSharedSecret(other)).toThrow(); }); }); -describe('Contact', () => { +describe("Contact", () => { let priv: PrivateKey; let pub: PublicKey; let identity: Identity; @@ -138,78 +140,79 @@ describe('Contact', () => { priv = PrivateKey.generate(); pub = priv.toPublicKey(); identity = new Identity(pub.toBytes()); - contact = new Contact('Alice', identity); + contact = new Contact("Alice", identity); }); - it('constructs from Identity', () => { + it("constructs from Identity", () => { expect(contact.identity).toBe(identity); - expect(contact.name).toBe('Alice'); + expect(contact.name).toBe("Alice"); }); - it('constructor throws on invalid type', () => { - expect(() => new (Contact as any)('X', 123)).toThrow(); + /* eslint-disable @typescript-eslint/no-explicit-any */ + it("constructor throws on invalid type", () => { + expect(() => new (Contact as any)("X", 123)).toThrow(); }); - it('constructs from Uint8Array', () => { - const c = new Contact('Bob', pub.toBytes()); + it("constructs from Uint8Array", () => { + const c = new Contact("Bob", pub.toBytes()); expect(c.identity.publicKey.toBytes()).toEqual(pub.toBytes()); }); - it('matches works', () => { + it("matches works", () => { expect(contact.matches(pub)).toBe(true); expect(contact.matches(pub.toBytes())).toBe(true); expect(contact.matches(new PublicKey(randomBytes(32)))).toBe(false); }); - it('publicKey returns PublicKey', () => { + it("publicKey returns PublicKey", () => { expect(contact.publicKey()).toBeInstanceOf(PublicKey); }); - it('calculateSharedSecret delegates', () => { + it("calculateSharedSecret delegates", () => { const me = new LocalIdentity(priv, pub); const secret = contact.calculateSharedSecret(me); expect(secret).toBeInstanceOf(SharedSecret); }); }); -describe('Group', () => { +describe("Group", () => { let group: Group; let secret: SharedSecret; let name: string; beforeEach(() => { - name = '#test'; + name = "#test"; secret = SharedSecret.fromName(name); group = new Group(name, secret); }); - it('constructs with and without secret', () => { + it("constructs with and without secret", () => { const g1 = new Group(name, secret); expect(g1).toBeInstanceOf(Group); const g2 = new Group(name); expect(g2).toBeInstanceOf(Group); }); - it('hash returns NodeHash', () => { - expect(typeof group.hash()).toBe('number'); + it("hash returns NodeHash", () => { + expect(typeof group.hash()).toBe("number"); }); - it('encryptText and decryptText roundtrip', () => { + it("encryptText and decryptText roundtrip", () => { const plain: DecryptedGroupText = { timestamp: new Date(), textType: 1, attempt: 2, - message: 'hello' + message: "hello" }; const { hmac, ciphertext } = group.encryptText(plain); const decrypted = group.decryptText(hmac, ciphertext); expect(decrypted.message).toBe(plain.message); expect(decrypted.textType).toBe(plain.textType); expect(decrypted.attempt).toBe(plain.attempt); - expect(typeof decrypted.timestamp).toBe('object'); + expect(typeof decrypted.timestamp).toBe("object"); }); - it('encryptData and decryptData roundtrip', () => { + it("encryptData and decryptData roundtrip", () => { const plain: DecryptedGroupData = { timestamp: new Date(), data: randomBytes(10) @@ -217,19 +220,19 @@ describe('Group', () => { const { hmac, ciphertext } = group.encryptData(plain); const decrypted = group.decryptData(hmac, ciphertext); expect(decrypted.data).toEqual(plain.data); - expect(typeof decrypted.timestamp).toBe('object'); + expect(typeof decrypted.timestamp).toBe("object"); }); - it('decryptText throws on short ciphertext', () => { + it("decryptText throws on short ciphertext", () => { expect(() => group.decryptText(randomBytes(16), Uint8Array.of(1, 2, 3, 4))).toThrow(); }); - it('decryptData throws on short ciphertext', () => { + it("decryptData throws on short ciphertext", () => { expect(() => group.decryptData(randomBytes(16), Uint8Array.of(1, 2, 3))).toThrow(); }); }); -describe('Contacts', () => { +describe("Contacts", () => { let contacts: Contacts; let local: LocalIdentity; let contact: Contact; @@ -240,27 +243,27 @@ describe('Contacts', () => { const priv = PrivateKey.generate(); const pub = priv.toPublicKey(); local = new LocalIdentity(priv, pub); - contact = new Contact('Alice', pub.toBytes()); - group = new Group('Public'); + contact = new Contact("Alice", pub.toBytes()); + group = new Group("Public"); }); - it('addLocalIdentity and addContact', () => { + it("addLocalIdentity and addContact", () => { contacts.addLocalIdentity(local); contacts.addContact(contact); // No error means success }); - it('addGroup', () => { + it("addGroup", () => { contacts.addGroup(group); }); - it('decryptGroupText and decryptGroupData', () => { + it("decryptGroupText and decryptGroupData", () => { contacts.addGroup(group); const text: DecryptedGroupText = { timestamp: new Date(), textType: 1, attempt: 0, - message: 'hi' + message: "hi" }; const { hmac, ciphertext } = group.encryptText(text); const res = contacts.decryptGroupText(group.hash(), hmac, ciphertext); @@ -275,22 +278,20 @@ describe('Contacts', () => { expect(res2.decrypted.data).toEqual(data.data); }); - it('decryptGroupText throws on unknown group', () => { + it("decryptGroupText throws on unknown group", () => { expect(() => contacts.decryptGroupText(0x99, randomBytes(16), randomBytes(16))).toThrow(); }); - it('decryptGroupData throws on unknown group', () => { + it("decryptGroupData throws on unknown group", () => { expect(() => contacts.decryptGroupData(0x99, randomBytes(16), randomBytes(16))).toThrow(); }); - it('decrypt throws on unknown source/destination', () => { + it("decrypt throws on unknown source/destination", () => { contacts.addLocalIdentity(local); - expect(() => - contacts.decrypt(0x99, 0x99, randomBytes(16), randomBytes(16)) - ).toThrow(); + expect(() => contacts.decrypt(0x99, 0x99, randomBytes(16), randomBytes(16))).toThrow(); }); - it('decrypt throws on decryption failure', () => { + it("decrypt throws on decryption failure", () => { contacts.addLocalIdentity(local); contacts.addContact(contact); expect(() => @@ -298,7 +299,7 @@ describe('Contacts', () => { ).toThrow(); }); - it('decrypt works for valid shared secret', () => { + it("decrypt works for valid shared secret", () => { // Setup two identities that can communicate const privA = PrivateKey.generate(); const pubA = privA.toPublicKey(); @@ -306,14 +307,14 @@ describe('Contacts', () => { const privB = PrivateKey.generate(); const pubB = privB.toPublicKey(); - const contactB = new Contact('Bob', pubB); + const contactB = new Contact("Bob", pubB); contacts.addLocalIdentity(localA); contacts.addContact(contactB); // Encrypt a message using the shared secret const shared = localA.calculateSharedSecret(contactB.identity); - const msg = Uint8Array.from([2,3,5,7,11,13,17,19,23,29,31,37]); + const msg = Uint8Array.from([2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]); const { hmac, ciphertext } = shared.encrypt(msg); const srcHash = contactB.identity.hash(); @@ -321,38 +322,46 @@ describe('Contacts', () => { const res = contacts.decrypt(srcHash, dstHash, hmac, ciphertext); expect(res.decrypted).toEqual(msg); - expect(res.contact.name).toBe('Bob'); + expect(res.contact.name).toBe("Bob"); expect(res.localIdentity.publicKey.toBytes()).toEqual(pubA.toBytes()); }); - it('group decryption falls back when first group fails', () => { + /* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-unused-vars */ + it("group decryption falls back when first group fails", () => { // Setup a group that will succeed and a fake one that throws - const name = '#fallback'; + const name = "#fallback"; const real = new Group(name); contacts.addGroup(real); const hash = real.hash(); const fake = { name, - decryptText: (_h: Uint8Array, _c: Uint8Array) => { throw new Error('fail'); } + decryptText: (_h: Uint8Array, _c: Uint8Array) => { + throw new Error("fail"); + } } as any; // Inject fake group before real one (contacts as any).groups[hash] = [fake, real]; - const text = { timestamp: new Date(), textType: 1, attempt: 0, message: 'hi' } as DecryptedGroupText; + const text = { timestamp: new Date(), textType: 1, attempt: 0, message: "hi" } as DecryptedGroupText; const enc = real.encryptText(text); const res = contacts.decryptGroupText(hash, enc.hmac, enc.ciphertext); expect(res.decrypted.message).toBe(text.message); expect(res.group).toBe(real); }); - it('group data decryption falls back when first group fails', () => { - const name = '#fallbackdata'; + /* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-unused-vars */ + it("group data decryption falls back when first group fails", () => { + const name = "#fallbackdata"; const real = new Group(name); contacts.addGroup(real); const hash = real.hash(); const fake = { name, - decryptData: (_h: Uint8Array, _c: Uint8Array) => { throw new Error('fail'); } + decryptData: (_h: Uint8Array, _c: Uint8Array) => { + throw new Error("fail"); + } } as any; (contacts as any).groups[hash] = [fake, real]; @@ -363,8 +372,8 @@ describe('Contacts', () => { expect(res.group).toBe(real); }); - it('decryptText throws when decrypted payload is too short', () => { - const name = '#short'; + it("decryptText throws when decrypted payload is too short", () => { + const name = "#short"; const secret = SharedSecret.fromName(name); const group = new Group(name, secret); // Create ciphertext that decrypts to a payload shorter than 5 bytes @@ -373,8 +382,8 @@ describe('Contacts', () => { expect(() => group.decryptText(enc.hmac, enc.ciphertext)).toThrow(/Invalid ciphertext/); }); - it('decryptData throws when decrypted payload is too short', () => { - const name = '#shortdata'; + it("decryptData throws when decrypted payload is too short", () => { + const name = "#shortdata"; const secret = SharedSecret.fromName(name); const group = new Group(name, secret); const small = new Uint8Array([1, 2, 3]); @@ -382,30 +391,48 @@ describe('Contacts', () => { expect(() => group.decryptData(enc.hmac, enc.ciphertext)).toThrow(/Invalid ciphertext/); }); - it('decrypt throws on unknown destination hash', () => { + it("decrypt throws on unknown destination hash", () => { contacts.addContact(contact); - expect(() => contacts.decrypt(contact.identity.hash(), 0x99, randomBytes(16), randomBytes(16))).toThrow(/Unknown destination hash/); + expect(() => contacts.decrypt(contact.identity.hash(), 0x99, randomBytes(16), randomBytes(16))).toThrow( + /Unknown destination hash/ + ); }); - it('decryptGroupText throws when all groups fail', () => { - const name = '#onlyfail'; + /* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-unused-vars */ + it("decryptGroupText throws when all groups fail", () => { + const name = "#onlyfail"; const group = new Group(name); const hash = group.hash(); - const fake = { decryptText: (_h: Uint8Array, _c: Uint8Array) => { throw new Error('fail'); } } as any; + const fake = { + decryptText: (_h: Uint8Array, _c: Uint8Array) => { + throw new Error("fail"); + } + } as any; (contacts as any).groups[hash] = [fake]; - expect(() => contacts.decryptGroupText(hash, randomBytes(16), randomBytes(16))).toThrow(/Decryption failed with all known groups/); + expect(() => contacts.decryptGroupText(hash, randomBytes(16), randomBytes(16))).toThrow( + /Decryption failed with all known groups/ + ); }); - it('decryptGroupData throws when all groups fail', () => { - const name = '#onlyfaildata'; + /* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-unused-vars */ + it("decryptGroupData throws when all groups fail", () => { + const name = "#onlyfaildata"; const group = new Group(name); const hash = group.hash(); - const fake = { decryptData: (_h: Uint8Array, _c: Uint8Array) => { throw new Error('fail'); } } as any; + const fake = { + decryptData: (_h: Uint8Array, _c: Uint8Array) => { + throw new Error("fail"); + } + } as any; (contacts as any).groups[hash] = [fake]; - expect(() => contacts.decryptGroupData(hash, randomBytes(16), randomBytes(16))).toThrow(/Decryption failed with all known groups/); + expect(() => contacts.decryptGroupData(hash, randomBytes(16), randomBytes(16))).toThrow( + /Decryption failed with all known groups/ + ); }); - it('decrypt accepts PublicKey as source', () => { + it("decrypt accepts PublicKey as source", () => { // Setup two identities that can communicate const privA = PrivateKey.generate(); const pubA = privA.toPublicKey(); @@ -413,7 +440,7 @@ describe('Contacts', () => { const privB = PrivateKey.generate(); const pubB = privB.toPublicKey(); - const contactB = new Contact('Bob', pubB); + const contactB = new Contact("Bob", pubB); contacts.addLocalIdentity(localA); contacts.addContact(contactB); @@ -424,34 +451,33 @@ describe('Contacts', () => { const res = contacts.decrypt(pubB, localA.publicKey.key[0], hmac, ciphertext); expect(res.decrypted).toEqual(msg); - expect(res.contact.name).toBe('Bob'); + expect(res.contact.name).toBe("Bob"); }); - it('decrypt with unknown PublicKey creates temporary contact and fails', () => { + it("decrypt with unknown PublicKey creates temporary contact and fails", () => { contacts.addLocalIdentity(local); const unknownPub = new PublicKey(randomBytes(32)); expect(() => contacts.decrypt(unknownPub, local.publicKey.key[0], randomBytes(16), randomBytes(16))).toThrow(); }); - it('LocalIdentity.calculateSharedSecret handles objects with publicKey variants', () => { + it("LocalIdentity.calculateSharedSecret handles objects with publicKey variants", () => { const priv = PrivateKey.generate(); const pub = priv.toPublicKey(); const localId = new LocalIdentity(priv, pub); - const obj1 = { publicKey: pub.toBytes() } as any; - const s1 = localId.calculateSharedSecret(obj1); + const s1 = localId.calculateSharedSecret(pub); expect(s1).toBeInstanceOf(SharedSecret); - const obj2 = { publicKey: pub } as any; + const obj2 = new Identity(pub.toBytes()); const s2 = localId.calculateSharedSecret(obj2); expect(s2).toBeInstanceOf(SharedSecret); - const obj3 = { publicKey: () => pub } as any; + const obj3 = new Identity(pub); const s3 = localId.calculateSharedSecret(obj3); expect(s3).toBeInstanceOf(SharedSecret); }); - it('decrypt uses cached shared secret on repeated attempts', () => { + it("decrypt uses cached shared secret on repeated attempts", () => { // Setup two identities that can communicate const privA = PrivateKey.generate(); const pubA = privA.toPublicKey(); @@ -459,7 +485,7 @@ describe('Contacts', () => { const privB = PrivateKey.generate(); const pubB = privB.toPublicKey(); - const contactB = new Contact('Bob', pubB); + const contactB = new Contact("Bob", pubB); contacts.addLocalIdentity(localA); contacts.addContact(contactB); diff --git a/test/packet.test.ts b/test/packet.test.ts index 74779e8..d030251 100644 --- a/test/packet.test.ts +++ b/test/packet.test.ts @@ -1,52 +1,70 @@ -import { describe, expect, test } from 'vitest'; -import { Packet } from '../src/packet'; -import { PayloadType, RouteType, NodeType, TracePayload, AdvertPayload, RequestPayload, TextPayload, ResponsePayload, RawCustomPayload, AnonReqPayload, Payload, AckPayload, PathPayload, GroupDataPayload, GroupTextPayload } from '../src/packet.types'; -import { hexToBytes, bytesToHex } from '../src/parser'; +import { describe, expect, test } from "vitest"; +import { Packet } from "../src/packet"; +import { + PayloadType, + RouteType, + NodeType, + TracePayload, + AdvertPayload, + RequestPayload, + TextPayload, + ResponsePayload, + RawCustomPayload, + AnonReqPayload, + Payload, + AckPayload, + PathPayload, + GroupDataPayload, + GroupTextPayload +} from "../src/packet.types"; +import { bytesToHex, Dissected, hexToBytes } from "@hamradio/packet"; -describe('Packet.fromBytes', () => { - test('frame 1: len=122 type=5 payload_len=99', () => { - const hex = '1515747207E0B28A52BE12186BCCBCABFC88A0417BBF78D951FF9FEC725F90F032C0DC9B7FD27890228B926A90E317E089F948EC66D9EF01F0C8683B6B28EC1E2D053741A75E7EEF51047BB4C9A1FB6766B379024DBA80B8FEFE804FF9696209039C2388E461AA6138D1DF9FDD3E333E5DFC18660F3E05F3364E'; +describe("Packet.fromBytes", () => { + test("frame 1: len=122 type=5 payload_len=99", () => { + const hex = + "1515747207E0B28A52BE12186BCCBCABFC88A0417BBF78D951FF9FEC725F90F032C0DC9B7FD27890228B926A90E317E089F948EC66D9EF01F0C8683B6B28EC1E2D053741A75E7EEF51047BB4C9A1FB6766B379024DBA80B8FEFE804FF9696209039C2388E461AA6138D1DF9FDD3E333E5DFC18660F3E05F3364E"; const bytes = hexToBytes(hex); expect(bytes.length).toBe(122); const pkt = Packet.fromBytes(bytes); expect(pkt.payload.length).toBe(99); expect(pkt.payloadType).toBe(PayloadType.GROUP_TEXT); const h = pkt.hash(); - expect(h.toUpperCase()).toBe('A17FC3ECD23FCFAD'); + expect(h.toUpperCase()).toBe("A17FC3ECD23FCFAD"); }); - test('frame 2: len=32 type=1 payload_len=20', () => { - const hex = '050AA50E2CB0336DB67BBF78928A3BB9BF7A8B677C83B6EC0716F9DD10002A06'; + test("frame 2: len=32 type=1 payload_len=20", () => { + const hex = "050AA50E2CB0336DB67BBF78928A3BB9BF7A8B677C83B6EC0716F9DD10002A06"; const bytes = hexToBytes(hex); expect(bytes.length).toBe(32); const pkt = Packet.fromBytes(bytes); expect(pkt.payload.length).toBe(20); expect(pkt.payloadType).toBe(PayloadType.RESPONSE); - expect(pkt.hash().toUpperCase()).toBe('1D378AD8B7EBA411'); + expect(pkt.hash().toUpperCase()).toBe("1D378AD8B7EBA411"); }); - test('frame 3: len=38 type=0 payload_len=20', () => { - const hex = '01104070B0331D9F19E44D36D5EECBC1BF78E8895A088C823AC61263D635A0AE1CF0FFAFF185'; + test("frame 3: len=38 type=0 payload_len=20", () => { + const hex = "01104070B0331D9F19E44D36D5EECBC1BF78E8895A088C823AC61263D635A0AE1CF0FFAFF185"; const bytes = hexToBytes(hex); expect(bytes.length).toBe(38); const pkt = Packet.fromBytes(bytes); expect(pkt.payload.length).toBe(20); expect(pkt.payloadType).toBe(PayloadType.REQUEST); - expect(pkt.hash().toUpperCase()).toBe('9948A57E8507EB95'); + expect(pkt.hash().toUpperCase()).toBe("9948A57E8507EB95"); }); - test('frame 4: len=37 type=8 payload_len=20', () => { - const hex = '210F95DE1A16E9726BBDAE4D36D5EEBF78B6C6157F5F75D077EA15FF2A7F4A354F12A7C7C5'; + test("frame 4: len=37 type=8 payload_len=20", () => { + const hex = "210F95DE1A16E9726BBDAE4D36D5EEBF78B6C6157F5F75D077EA15FF2A7F4A354F12A7C7C5"; const bytes = hexToBytes(hex); expect(bytes.length).toBe(37); const pkt = Packet.fromBytes(bytes); expect(pkt.payload.length).toBe(20); expect(pkt.payloadType).toBe(PayloadType.PATH); - expect(pkt.hash().toUpperCase()).toBe('0A5157C46F34ECC1'); + expect(pkt.hash().toUpperCase()).toBe("0A5157C46F34ECC1"); }); - test('frame 5: len=26 type=3 payload_len=20', () => { - const hex = '2742FD6C4C3B1A35248B823B6CA2BAF2E93DC8F3B8A895ED868B68BFB04986C04E078166A7F5651F0872538123199FD4FE910948DA5361FF5E8CB5ACB1A2AC4220D2101FC9ACCB30B990D4EFC2C163B578BAAE15FF5DC216539648B87108764945DFC888BFC04F0C28B3410DF844993D8F23EF83DE4B131E52966C5F110F46'; + test("frame 5: len=26 type=3 payload_len=20", () => { + const hex = + "2742FD6C4C3B1A35248B823B6CA2BAF2E93DC8F3B8A895ED868B68BFB04986C04E078166A7F5651F0872538123199FD4FE910948DA5361FF5E8CB5ACB1A2AC4220D2101FC9ACCB30B990D4EFC2C163B578BAAE15FF5DC216539648B87108764945DFC888BFC04F0C28B3410DF844993D8F23EF83DE4B131E52966C5F110F46"; const bytes = hexToBytes(hex); const pkt = Packet.fromBytes(bytes); expect(pkt.routeType).toBe(RouteType.TRANSPORT_DIRECT); @@ -56,37 +74,42 @@ describe('Packet.fromBytes', () => { // the TRACE payload format has been updated; ensure we decode a TRACE payload expect(payload.type).toBe(PayloadType.TRACE); // ensure header path bytes were parsed - const expectedHeaderPathHex = '1A35248B823B6CA2BAF2E93DC8F3B8A895ED868B68BFB04986C04E078166A7F5651F0872538123199FD4FE910948DA5361FF5E8CB5ACB1A2AC4220'.toUpperCase(); + const expectedHeaderPathHex = + "1A35248B823B6CA2BAF2E93DC8F3B8A895ED868B68BFB04986C04E078166A7F5651F0872538123199FD4FE910948DA5361FF5E8CB5ACB1A2AC4220".toUpperCase(); expect(bytesToHex(pkt.path).toUpperCase()).toBe(expectedHeaderPathHex); // transport codes (big-endian words as parsed from the packet) expect(pkt.transport).toEqual([0x42fd, 0x6c4c]); expect(pkt.pathLength).toBe(0x3b); // payload bytes check (raw payload must match expected) - const expectedPayloadHex = 'D2101FC9ACCB30B990D4EFC2C163B578BAAE15FF5DC216539648B87108764945DFC888BFC04F0C28B3410DF844993D8F23EF83DE4B131E52966C5F110F46'.toUpperCase(); + const expectedPayloadHex = + "D2101FC9ACCB30B990D4EFC2C163B578BAAE15FF5DC216539648B87108764945DFC888BFC04F0C28B3410DF844993D8F23EF83DE4B131E52966C5F110F46".toUpperCase(); expect(bytesToHex(pkt.payload).toUpperCase()).toBe(expectedPayloadHex); // verify decoded trace fields: tag, authCode, flags and nodes const trace = payload as TracePayload; // tag/auth are read as little-endian uint32 values (memcpy on little-endian C) - expect(trace.tag).toBe(0xC91F10D2); - expect(trace.authCode).toBe(0xB930CBAC); + expect(trace.tag).toBe(0xc91f10d2); + expect(trace.authCode).toBe(0xb930cbac); // expect(trace.flags).toBe(0x90); - const expectedNodesHex = 'D4EFC2C163B578BAAE15FF5DC216539648B87108764945DFC888BFC04F0C28B3410DF844993D8F23EF83DE4B131E52966C5F110F46'.toUpperCase(); + const expectedNodesHex = + "D4EFC2C163B578BAAE15FF5DC216539648B87108764945DFC888BFC04F0C28B3410DF844993D8F23EF83DE4B131E52966C5F110F46".toUpperCase(); expect(bytesToHex(trace.nodes).toUpperCase()).toBe(expectedNodesHex); }); - test('frame 6: len=110 type=1 payload_len=99', () => { - const hex = '1102607BE88177A117AE4391668509349D30A76FBA92E90CB9B1A75F49AC3382FED4E773336056663D9B84598A431A9ABE05D4F5214DF358133D8EB7022B63B92829335E8D5742B3249477744411BDC1E6664D3BAAAF170E50DF91F07D6E68FAE8A34616030E8F0992143711038C3953004E4C2D4548562D564247422D52505452'; + test("frame 6: len=110 type=1 payload_len=99", () => { + const hex = + "1102607BE88177A117AE4391668509349D30A76FBA92E90CB9B1A75F49AC3382FED4E773336056663D9B84598A431A9ABE05D4F5214DF358133D8EB7022B63B92829335E8D5742B3249477744411BDC1E6664D3BAAAF170E50DF91F07D6E68FAE8A34616030E8F0992143711038C3953004E4C2D4548562D564247422D52505452"; const bytes = hexToBytes(hex); const pkt = Packet.fromBytes(bytes); expect(pkt.routeType).toBe(RouteType.FLOOD); expect(pkt.payloadType).toBe(PayloadType.ADVERT); const adv = pkt.decode() as AdvertPayload; expect(adv.type).toBe(PayloadType.ADVERT); - const pubHex = 'E88177A117AE4391668509349D30A76FBA92E90CB9B1A75F49AC3382FED4E773'; + const pubHex = "E88177A117AE4391668509349D30A76FBA92E90CB9B1A75F49AC3382FED4E773"; expect(bytesToHex(adv.publicKey).toUpperCase()).toBe(pubHex); // timestamp should match 2024-05-28T22:52:35Z - expect(adv.timestamp.toISOString()).toBe('2024-05-28T22:52:35.000Z'); - const sigHex = '3D9B84598A431A9ABE05D4F5214DF358133D8EB7022B63B92829335E8D5742B3249477744411BDC1E6664D3BAAAF170E50DF91F07D6E68FAE8A34616030E8F09'; + expect(adv.timestamp.toISOString()).toBe("2024-05-28T22:52:35.000Z"); + const sigHex = + "3D9B84598A431A9ABE05D4F5214DF358133D8EB7022B63B92829335E8D5742B3249477744411BDC1E6664D3BAAAF170E50DF91F07D6E68FAE8A34616030E8F09"; expect(bytesToHex(adv.signature).toUpperCase()).toBe(sigHex); // appdata flags 0x92 -> nodeType 0x02 (REPEATER), hasLocation true, hasName true expect(adv.appdata.nodeType).toBe(NodeType.REPEATER); @@ -96,13 +119,19 @@ describe('Packet.fromBytes', () => { expect(adv.appdata.location).toBeDefined(); expect(adv.appdata.location![0] / 10).toBeCloseTo(5.145986, 5); expect(adv.appdata.location![1] / 10).toBeCloseTo(0.545422, 5); - expect(adv.appdata.name).toBe('NL-EHV-VBGB-RPTR'); - expect(pkt.hash().toUpperCase()).toBe('67C10F75168ECC8C'); + expect(adv.appdata.name).toBe("NL-EHV-VBGB-RPTR"); + expect(pkt.hash().toUpperCase()).toBe("67C10F75168ECC8C"); }); }); -describe('Packet decode branches and transport/path parsing', () => { - const makePacket = (payloadType: number, routeType: number, pathBytes: Uint8Array, payload: Uint8Array, transportWords?: [number, number]) => { +describe("Packet decode branches and transport/path parsing", () => { + const makePacket = ( + payloadType: number, + routeType: number, + pathBytes: Uint8Array, + payload: Uint8Array, + transportWords?: [number, number] + ) => { const header = (0 << 6) | (payloadType << 2) | routeType; const parts: number[] = [header]; if (transportWords) { @@ -119,47 +148,61 @@ describe('Packet decode branches and transport/path parsing', () => { return arr; }; - test('hasTransportCodes true/false and transport parsed', () => { + test("hasTransportCodes true/false and transport parsed", () => { // transport present (route TRANSPORT_FLOOD = 0) - const p = makePacket(PayloadType.REQUEST, RouteType.TRANSPORT_FLOOD, new Uint8Array([]), new Uint8Array([0,0,1,2]), [0x1122, 0x3344]); + const p = makePacket( + PayloadType.REQUEST, + RouteType.TRANSPORT_FLOOD, + new Uint8Array([]), + new Uint8Array([0, 0, 1, 2]), + [0x1122, 0x3344] + ); const pkt = Packet.fromBytes(p); expect(pkt.transport).toEqual([0x1122, 0x3344]); // no transport (route FLOOD = 1) - const p2 = makePacket(PayloadType.REQUEST, RouteType.FLOOD, new Uint8Array([]), new Uint8Array([0,0,1,2])); + const p2 = makePacket(PayloadType.REQUEST, RouteType.FLOOD, new Uint8Array([]), new Uint8Array([0, 0, 1, 2])); const pkt2 = Packet.fromBytes(p2); expect(pkt2.transport).toBeUndefined(); }); - test('payload REQUEST/RESPONSE/TEXT decode (encrypted parsing)', () => { - const payload = new Uint8Array([0xAA, 0xBB, 0x01, 0x02, 0x03]); // dst,src, mac(2), cipherText(1) + test("payload REQUEST/RESPONSE/TEXT decode (encrypted parsing)", () => { + const payload = new Uint8Array([0xaa, 0xbb, 0x01, 0x02, 0x03]); // dst,src, mac(2), cipherText(1) const pkt = Packet.fromBytes(makePacket(PayloadType.REQUEST, RouteType.DIRECT, new Uint8Array([]), payload)); const req = pkt.decode() as RequestPayload; expect(req.type).toBe(PayloadType.REQUEST); - expect(req.dst).toBe(0xAA); - expect(req.src).toBe(0xBB); + expect(req.dst).toBe(0xaa); + expect(req.src).toBe(0xbb); - const resp = Packet.fromBytes(makePacket(PayloadType.RESPONSE, RouteType.DIRECT, new Uint8Array([]), payload)).decode() as ResponsePayload; + const resp = Packet.fromBytes( + makePacket(PayloadType.RESPONSE, RouteType.DIRECT, new Uint8Array([]), payload) + ).decode() as ResponsePayload; expect(resp.type).toBe(PayloadType.RESPONSE); - const txt = Packet.fromBytes(makePacket(PayloadType.TEXT, RouteType.DIRECT, new Uint8Array([]), payload)).decode() as TextPayload; + const txt = Packet.fromBytes( + makePacket(PayloadType.TEXT, RouteType.DIRECT, new Uint8Array([]), payload) + ).decode() as TextPayload; expect(txt.type).toBe(PayloadType.TEXT); }); - test('ACK decode and RAW_CUSTOM', () => { - const ackPayload = new Uint8Array([0x01,0x02,0x03,0x04]); - const ack = Packet.fromBytes(makePacket(PayloadType.ACK, RouteType.DIRECT, new Uint8Array([]), ackPayload)).decode() as AckPayload; + test("ACK decode and RAW_CUSTOM", () => { + const ackPayload = new Uint8Array([0x01, 0x02, 0x03, 0x04]); + const ack = Packet.fromBytes( + makePacket(PayloadType.ACK, RouteType.DIRECT, new Uint8Array([]), ackPayload) + ).decode() as AckPayload; expect(ack.type).toBe(PayloadType.ACK); - const custom = new Uint8Array([0x99,0x88,0x77]); - const rc = Packet.fromBytes(makePacket(PayloadType.RAW_CUSTOM, RouteType.DIRECT, new Uint8Array([]), custom)).decode() as RawCustomPayload; + const custom = new Uint8Array([0x99, 0x88, 0x77]); + const rc = Packet.fromBytes( + makePacket(PayloadType.RAW_CUSTOM, RouteType.DIRECT, new Uint8Array([]), custom) + ).decode() as RawCustomPayload; expect(rc.type).toBe(PayloadType.RAW_CUSTOM); expect(rc.data).toEqual(custom); }); - test('ADVERT minimal decode (no appdata extras)', () => { + test("ADVERT minimal decode (no appdata extras)", () => { const publicKey = new Uint8Array(32).fill(1); - const timestamp = new Uint8Array([0x01,0x00,0x00,0x00]); + const timestamp = new Uint8Array([0x01, 0x00, 0x00, 0x00]); const signature = new Uint8Array(64).fill(2); const flags = new Uint8Array([0x00]); const payload = new Uint8Array([...publicKey, ...timestamp, ...signature, ...flags]); @@ -171,46 +214,56 @@ describe('Packet decode branches and transport/path parsing', () => { expect(adv.appdata.hasName).toBe(false); }); - test('GROUP_TEXT and GROUP_DATA decode', () => { + test("GROUP_TEXT and GROUP_DATA decode", () => { const payload = new Uint8Array([0x55, 0x01, 0x02, 0x03]); // channelHash + mac(2) + cipher - const gt = Packet.fromBytes(makePacket(PayloadType.GROUP_TEXT, RouteType.DIRECT, new Uint8Array([]), payload)).decode() as GroupTextPayload; + const gt = Packet.fromBytes( + makePacket(PayloadType.GROUP_TEXT, RouteType.DIRECT, new Uint8Array([]), payload) + ).decode() as GroupTextPayload; expect(gt.type).toBe(PayloadType.GROUP_TEXT); - const gd = Packet.fromBytes(makePacket(PayloadType.GROUP_DATA, RouteType.DIRECT, new Uint8Array([]), payload)).decode() as GroupDataPayload; + const gd = Packet.fromBytes( + makePacket(PayloadType.GROUP_DATA, RouteType.DIRECT, new Uint8Array([]), payload) + ).decode() as GroupDataPayload; expect(gd.type).toBe(PayloadType.GROUP_DATA); }); - test('ANON_REQ decode', () => { + test("ANON_REQ decode", () => { const dst = 0x12; const pub = new Uint8Array(32).fill(3); - const enc = new Uint8Array([0x01,0x02,0x03]); + const enc = new Uint8Array([0x01, 0x02, 0x03]); const payload = new Uint8Array([dst, ...pub, ...enc]); - const ar = Packet.fromBytes(makePacket(PayloadType.ANON_REQ, RouteType.DIRECT, new Uint8Array([]), payload)).decode() as AnonReqPayload; + const ar = Packet.fromBytes( + makePacket(PayloadType.ANON_REQ, RouteType.DIRECT, new Uint8Array([]), payload) + ).decode() as AnonReqPayload; expect(ar.type).toBe(PayloadType.ANON_REQ); expect(ar.dst).toBe(0x12); }); - test('PATH and TRACE decode nodes', () => { + test("PATH and TRACE decode nodes", () => { const pathPayload = new Uint8Array([0x0a, 0x0b]); - const path = Packet.fromBytes(makePacket(PayloadType.PATH, RouteType.DIRECT, new Uint8Array([]), pathPayload)).decode() as PathPayload; + const path = Packet.fromBytes( + makePacket(PayloadType.PATH, RouteType.DIRECT, new Uint8Array([]), pathPayload) + ).decode() as PathPayload; expect(path.type).toBe(PayloadType.PATH); - const nodes = new Uint8Array([0x01,0x02,0x03]); + const nodes = new Uint8Array([0x01, 0x02, 0x03]); // construct TRACE payload: tag (4 bytes LE), authCode (4 bytes LE), flags (1), nodes... - const tag = new Uint8Array([0x01,0x00,0x00,0x00]); - const auth = new Uint8Array([0x02,0x00,0x00,0x00]); + const tag = new Uint8Array([0x01, 0x00, 0x00, 0x00]); + const auth = new Uint8Array([0x02, 0x00, 0x00, 0x00]); const flags = new Uint8Array([0x00]); const tracePayload = new Uint8Array([...tag, ...auth, ...flags, ...nodes]); - const trace = Packet.fromBytes(makePacket(PayloadType.TRACE, RouteType.DIRECT, new Uint8Array([]), tracePayload)).decode() as TracePayload; + const trace = Packet.fromBytes( + makePacket(PayloadType.TRACE, RouteType.DIRECT, new Uint8Array([]), tracePayload) + ).decode() as TracePayload; expect(trace.type).toBe(PayloadType.TRACE); expect(trace.nodes).toBeInstanceOf(Uint8Array); }); - test('pathHashes parsing when multiple hashes', () => { + test("pathHashes parsing when multiple hashes", () => { // create pathLength byte: count=2 size=3 -> (1<<6)|3 = 67 const pathLengthByte = 67; const header = (0 << 6) | (PayloadType.RAW_CUSTOM << 2) | RouteType.DIRECT; const payload = new Uint8Array([0x01]); - const pathBytes = new Uint8Array([0xAA,0xBB,0xCC, 0x11,0x22,0x33]); + const pathBytes = new Uint8Array([0xaa, 0xbb, 0xcc, 0x11, 0x22, 0x33]); const parts: number[] = [header, pathLengthByte]; const arr = new Uint8Array(parts.length + pathBytes.length + payload.length); arr.set(parts, 0); @@ -220,10 +273,10 @@ describe('Packet decode branches and transport/path parsing', () => { expect(pkt.pathHashCount).toBe(3); expect(pkt.pathHashSize).toBe(2); expect(pkt.pathHashes.length).toBe(3); - expect(pkt.pathHashes[0]).toBe(bytesToHex(pathBytes.subarray(0,2))); + expect(pkt.pathHashes[0]).toBe(bytesToHex(pathBytes.subarray(0, 2))); }); - test('unsupported payload type throws', () => { + test("unsupported payload type throws", () => { // payloadType 0x0a is not handled const header = (0 << 6) | (0x0a << 2) | RouteType.DIRECT; const arr = new Uint8Array([header, 0x00]); @@ -233,18 +286,18 @@ describe('Packet decode branches and transport/path parsing', () => { }); describe("Packet.decode overloads", () => { - const ackBytes = new Uint8Array([ /* header */ 13, /* pathLength */ 0, /* payload (4 bytes checksum) */ 1, 2, 3, 4 ]); + const ackBytes = new Uint8Array([/* header */ 13, /* pathLength */ 0, /* payload (4 bytes checksum) */ 1, 2, 3, 4]); test("decode() returns payload only", () => { const pkt = Packet.fromBytes(ackBytes); const payload = pkt.decode() as Payload; expect(payload.type).toBe(PayloadType.ACK); - expect((payload as any).checksum).toEqual(new Uint8Array([1, 2, 3, 4])); + expect((payload as AckPayload).checksum).toEqual(new Uint8Array([1, 2, 3, 4])); }); test("decode(true) returns { payload, structure }", () => { const pkt = Packet.fromBytes(ackBytes); - const res = pkt.decode(true) as any; + const res = pkt.decode(true) as unknown as { payload: Payload; structure: Dissected }; expect(res).toHaveProperty("payload"); expect(res).toHaveProperty("structure"); expect(res.payload.type).toBe(PayloadType.ACK); diff --git a/test/parser.test.ts b/test/parser.test.ts deleted file mode 100644 index e77662a..0000000 --- a/test/parser.test.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { base64ToBytes, hexToBytes, BufferReader, BufferWriter } from '../src/parser'; - -describe('base64ToBytes', () => { - it('decodes a simple base64 string', () => { - const bytes = base64ToBytes('aGVsbG8=', 5); // "hello" - expect(Array.from(bytes)).toEqual([104, 101, 108, 108, 111]); - }); - - it('handles empty string', () => { - const bytes = base64ToBytes('', 0); - expect(bytes).toBeInstanceOf(Uint8Array); - expect(bytes.length).toBe(0); - }); -}); - -describe('BufferReader', () => { - it('readByte and peekByte advance/inspect correctly', () => { - const buf = new Uint8Array([1, 2, 3]); - const r = new BufferReader(buf); - expect(r.peekByte()).toBe(1); - expect(r.readByte()).toBe(1); - expect(r.peekByte()).toBe(2); - }); - - it('readBytes with and without length', () => { - const buf = new Uint8Array([10, 11, 12, 13]); - const r = new BufferReader(buf); - const a = r.readBytes(2); - expect(Array.from(a)).toEqual([10, 11]); - const b = r.readBytes(); - expect(Array.from(b)).toEqual([12, 13]); - }); - - it('hasMore and remainingBytes reflect position', () => { - const buf = new Uint8Array([5, 6]); - const r = new BufferReader(buf); - expect(r.hasMore()).toBe(true); - expect(r.remainingBytes()).toBe(2); - r.readByte(); - expect(r.remainingBytes()).toBe(1); - r.readByte(); - expect(r.hasMore()).toBe(false); - }); - - it('reads little-endian unsigned ints', () => { - const r16 = new BufferReader(new Uint8Array([0x34, 0x12])); - expect(r16.readUint16LE()).toBe(0x1234); - - const r32 = new BufferReader(new Uint8Array([0x78, 0x56, 0x34, 0x12])); - expect(r32.readUint32LE()).toBe(0x12345678); - }); - - it('reads signed ints with two/four bytes (negative)', () => { - const r16 = new BufferReader(new Uint8Array([0xff, 0xff])); - expect(r16.readInt16LE()).toBe(-1); - - const r32 = new BufferReader(new Uint8Array([0xff, 0xff, 0xff, 0xff])); - expect(r32.readInt32LE()).toBe(-1); - }); - - it('readTimestamp returns Date with seconds->ms conversion', () => { - const r = new BufferReader(new Uint8Array([0x01, 0x00, 0x00, 0x00])); - const d = r.readTimestamp(); - expect(d.getTime()).toBe(1000); - }); -}); -describe('sizedStringToBytes', () => { - it('decodes hex string of correct length', () => { - // 4 bytes = 8 hex chars - const hex = 'deadbeef'; - const result = hexToBytes(hex, 4); - expect(Array.from(result)).toEqual([0xde, 0xad, 0xbe, 0xef]); - }); - - it('decodes base64 string of correct length', () => { - // 4 bytes = 8 hex chars, base64 for [0xde, 0xad, 0xbe, 0xef] is '3q2+7w==' - const b64 = '3q2+7w=='; - const result = base64ToBytes(b64, 4); - expect(Array.from(result)).toEqual([0xde, 0xad, 0xbe, 0xef]); - }); - - it('throws on invalid string length', () => { - expect(() => hexToBytes('abc', 4)).toThrow(); - expect(() => hexToBytes('deadbeef00', 4)).toThrow(); - }); -}); - -describe('BufferWriter', () => { - it('writeByte and toBytes', () => { - const w = new BufferWriter(); - w.writeByte(0x12); - w.writeByte(0x34); - expect(Array.from(w.toBytes())).toEqual([0x12, 0x34]); - }); - - it('writeBytes appends bytes', () => { - const w = new BufferWriter(); - w.writeBytes(new Uint8Array([1, 2, 3])); - expect(Array.from(w.toBytes())).toEqual([1, 2, 3]); - }); - - it('writeUint16LE writes little-endian', () => { - const w = new BufferWriter(); - w.writeUint16LE(0x1234); - expect(Array.from(w.toBytes())).toEqual([0x34, 0x12]); - }); - - it('writeUint32LE writes little-endian', () => { - const w = new BufferWriter(); - w.writeUint32LE(0x12345678); - expect(Array.from(w.toBytes())).toEqual([0x78, 0x56, 0x34, 0x12]); - }); - - it('writeInt16LE writes signed values', () => { - const w = new BufferWriter(); - w.writeInt16LE(-1); - expect(Array.from(w.toBytes())).toEqual([0xff, 0xff]); - const w2 = new BufferWriter(); - w2.writeInt16LE(0x1234); - expect(Array.from(w2.toBytes())).toEqual([0x34, 0x12]); - }); - - it('writeInt32LE writes signed values', () => { - const w = new BufferWriter(); - w.writeInt32LE(-1); - expect(Array.from(w.toBytes())).toEqual([0xff, 0xff, 0xff, 0xff]); - const w2 = new BufferWriter(); - w2.writeInt32LE(0x12345678); - expect(Array.from(w2.toBytes())).toEqual([0x78, 0x56, 0x34, 0x12]); - }); - - it('writeTimestamp writes seconds as uint32le', () => { - const w = new BufferWriter(); - const date = new Date(1000); // 1 second - w.writeTimestamp(date); - expect(Array.from(w.toBytes())).toEqual([0x01, 0x00, 0x00, 0x00]); - }); - - it('BufferWriter output can be read back by BufferReader', () => { - const w = new BufferWriter(); - w.writeByte(0x42); - w.writeUint16LE(0x1234); - w.writeInt16LE(-2); - w.writeUint32LE(0xdeadbeef); - w.writeInt32LE(-123456); - w.writeBytes(new Uint8Array([0x01, 0x02])); - const date = new Date(5000); // 5 seconds - w.writeTimestamp(date); - - const bytes = w.toBytes(); - const r = new BufferReader(bytes); - - expect(r.readByte()).toBe(0x42); - expect(r.readUint16LE()).toBe(0x1234); - expect(r.readInt16LE()).toBe(-2); - expect(r.readUint32LE()).toBe(0xdeadbeef); - expect(r.readInt32LE()).toBe(-123456); - expect(Array.from(r.readBytes(2))).toEqual([0x01, 0x02]); - const readDate = r.readTimestamp(); - expect(readDate.getTime()).toBe(5000); - expect(r.hasMore()).toBe(false); - }); - - it('BufferReader throws or returns undefined if reading past end', () => { - const r = new BufferReader(new Uint8Array([1, 2])); - r.readByte(); - r.readByte(); - expect(() => r.readByte()).toThrow(); - }); - - it('BufferWriter handles multiple writeBytes calls', () => { - const w = new BufferWriter(); - w.writeBytes(new Uint8Array([1, 2])); - w.writeBytes(new Uint8Array([3, 4])); - expect(Array.from(w.toBytes())).toEqual([1, 2, 3, 4]); - }); - - it('encodedStringToBytes decodes raw string', () => { - const str = String.fromCharCode(0xde, 0xad, 0xbe, 0xef); - const bytes = new Uint8Array(4); - for (let i = 0; i < 4; i++) bytes[i] = str.charCodeAt(i) & 0xff; - expect(Array.from(bytes)).toEqual([0xde, 0xad, 0xbe, 0xef]); - }); - - it('hexToBytes returns different length for wrong-size hex', () => { - expect(() => hexToBytes('deadbe', 4)).toThrow(); - }); - - it('base64ToBytes handles URL-safe base64', () => { - // [0xde, 0xad, 0xbe, 0xef] in URL-safe base64: '3q2-7w==' - const bytes = base64ToBytes('3q2-7w==', 4); - expect(Array.from(bytes)).toEqual([0xde, 0xad, 0xbe, 0xef]); - }); -});