diff --git a/README.md b/README.md new file mode 100644 index 0000000..be1ee66 --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ +# WebSDR + +[WebSDR](https://github.com/wavelet-lab/websdr) is an open-source TypeScript monorepo that provides libraries and tools for building web applications that work with Software Defined Radios (SDR) using WebUSB and related browser/node tooling. + +⚠️ This project is under active development. Some features are experimental and APIs may change. + +## What is WebSDR? + +WebSDR contains utilities, UI components, backend modules, and small test apps to make it easier to build browser-based SDR applications and tooling. The primary goal is to enable interaction with SDR devices connected over USB from web applications (via WebUSB), and to provide supporting building blocks for dashboards, demos, and server-side microservices. + +Core capabilities include: +- WebUSB device management (requesting devices, selecting devices in UI components). +- A small Vue 3 component library for dashboards and controls (dropdowns, lists, inputs, log viewers). +- NestJS modules for microservices (authentication, API scaffolding) useful for backend parts of an SDR web platform. +- Utility modules: circular buffers, data conversion helpers, string utilities, time helpers and promise helpers used across frontend and backend. + +## Repository layout + +Top-level structure (important folders): + +``` +packages/ +├─ core/ # Shared domain types, utilities, constants +├─ frontend-core/ # Front-end utilities and WebUSB adapters +├─ vue3-components/ # Reusable Vue 3 UI components and styles +├─ nestjs-microservice/ # NestJS modules (auth, API helpers, microservice wiring) +test-apps/ # Small example/test applications and scripts +``` + +Brief package descriptions: +- `packages/core` — Core shared library with domain types, utilities, constants and radio-related helpers. +- `packages/frontend-core` — Front-end core utilities, WebUSB adapters, and services used by client apps. +- `packages/vue3-components` — Vue 3 component library (examples: `SdrInput`, `Dropdown`, `List`, `LogArea`). Built with Vite; ships TypeScript types and styles. +- `packages/nestjs-microservice` — NestJS integration and helper modules; main entry is `WebSDRModule` (configurable via environment variables such as `WEBSDR_*`). +- `test-apps` — Small scripts and demo pages used to test low-level functionality (e.g., `usb-test.ts`). + +## Quick setup + +Install dependencies for the workspace: + +```bash +npm install +``` + +Build the packages: + +```bash +npm run build +``` + +Run tests (runs workspace tests): + +```bash +npm run test +``` + +Run all tests +```bash +npm run test:all +``` + +Run all tests and watch for changes +```bash +npm run test:watch +``` + +Run coverage check +```bash +npm run test:coverage +``` + +## Running test applications + +Example test apps live in `test-apps`. To run them: + +```bash +cd test-apps +npm install +npm run test:usb # runs the USB test script (requires appropriate permissions/flags) +``` + +Note: WebUSB requires HTTPS or localhost and browser support. Running tests that access USB devices may require additional browser flags or permissions. + +## Environment / Configuration + +- `packages/nestjs-microservice` reads configuration from environment variables (prefixed with `WEBSDR_` in places). See the module entry and code for exact variable names. + +## License + +WebSDR is [MIT licensed](https://github.com/wavelet-lab/websdr/blob/main/LICENSE) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9328151 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,12784 @@ +{ + "name": "websdr", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "websdr", + "version": "1.0.0", + "license": "MIT", + "workspaces": [ + "./packages/core", + "./packages/nestjs-microservice", + "./packages/frontend-core", + "./packages/vue3-components" + ], + "devDependencies": { + "@vitest/coverage-v8": "^4.0.7", + "tsc-alias": "^1.8.16", + "tsc-esm-fix": "^3.1.2", + "typescript": "^5.9.3", + "vite": "^7.2.1", + "vitest": "^4.0.7" + } + }, + "node_modules/@achrinza/node-ipc": { + "version": "9.2.9", + "resolved": "https://registry.npmjs.org/@achrinza/node-ipc/-/node-ipc-9.2.9.tgz", + "integrity": "sha512-7s0VcTwiK/0tNOVdSX9FWMeFdOEcsAOz9HesBldXxFMaGvIak7KC2z9tV9EgsQXn6KUsWsfIkViMNuIo0GoZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@node-ipc/js-queue": "2.0.3", + "event-pubsub": "4.3.0", + "js-message": "1.0.7" + }, + "engines": { + "node": "8 || 9 || 10 || 11 || 12 || 13 || 14 || 15 || 16 || 17 || 18 || 19 || 20 || 21 || 22" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "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/@borewit/text-codec": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", + "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@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/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "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==", + "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/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/common": { + "version": "11.1.8", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.8.tgz", + "integrity": "sha512-bbsOqwld/GdBfiRNc4nnjyWWENDEicq4SH+R5AuYatvf++vf1x5JIsHB1i1KtfZMD3eRte0D4K9WXuAYil6XAg==", + "license": "MIT", + "peer": true, + "dependencies": { + "file-type": "21.0.0", + "iterare": "1.2.1", + "load-esm": "1.0.3", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "class-transformer": ">=0.4.1", + "class-validator": ">=0.13.2", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.2.tgz", + "integrity": "sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==", + "license": "MIT", + "dependencies": { + "dotenv": "16.4.7", + "dotenv-expand": "12.0.1", + "lodash": "4.17.21" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/core": { + "version": "11.1.8", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.8.tgz", + "integrity": "sha512-7riWfmTmMhCJHZ5ZiaG+crj4t85IPCq/wLRuOUSigBYyFT2JZj0lVHtAdf4Davp9ouNI8GINBDt9h9b5Gz9nTw==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@nuxt/opencollective": "0.4.1", + "fast-safe-stringify": "2.1.1", + "iterare": "1.2.1", + "path-to-regexp": "8.3.0", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "engines": { + "node": ">= 20" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/microservices": "^11.0.0", + "@nestjs/platform-express": "^11.0.0", + "@nestjs/websockets": "^11.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + }, + "@nestjs/websockets": { + "optional": true + } + } + }, + "node_modules/@nestjs/jwt": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.1.tgz", + "integrity": "sha512-HXSsc7SAnCnjA98TsZqrE7trGtHDnYXWp4Ffy6LwSmck1QvbGYdMzBquXofX5l6tIRpeY4Qidl2Ti2CVG77Pdw==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "9.0.10", + "jsonwebtoken": "9.0.2" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" + } + }, + "node_modules/@nestjs/microservices": { + "version": "11.1.8", + "resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-11.1.8.tgz", + "integrity": "sha512-AybVmZHzJtSnf3igmwH79FpwFjPZejtWTcWP29bT7QoPmDPg5A7/hPuWA6/Wf2cD6U37EAaX83+R4jCnR5E+6A==", + "license": "MIT", + "peer": true, + "dependencies": { + "iterare": "1.2.1", + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@grpc/grpc-js": "*", + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0", + "@nestjs/websockets": "^11.0.0", + "amqp-connection-manager": "*", + "amqplib": "*", + "cache-manager": "*", + "ioredis": "*", + "kafkajs": "*", + "mqtt": "*", + "nats": "*", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@grpc/grpc-js": { + "optional": true + }, + "@nestjs/websockets": { + "optional": true + }, + "amqp-connection-manager": { + "optional": true + }, + "amqplib": { + "optional": true + }, + "cache-manager": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "kafkajs": { + "optional": true + }, + "mqtt": { + "optional": true + }, + "nats": { + "optional": true + } + } + }, + "node_modules/@nestjs/passport": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz", + "integrity": "sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "passport": "^0.5.0 || ^0.6.0 || ^0.7.0" + } + }, + "node_modules/@nestjs/testing": { + "version": "11.1.8", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.8.tgz", + "integrity": "sha512-E6K+0UTKztcPxJzLnQa7S34lFjZbrj3Z1r7c5y5WDrL1m5HD1H4AeyBhicHgdaFmxjLAva2bq0sYKy/S7cdeYA==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0", + "@nestjs/microservices": "^11.0.0", + "@nestjs/platform-express": "^11.0.0" + }, + "peerDependenciesMeta": { + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + } + } + }, + "node_modules/@nestjs/websockets": { + "version": "11.1.8", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.1.8.tgz", + "integrity": "sha512-RXo2336p/vyAwJ0qPInglzNSQ//qz+JTLr2LE1vlbmN5WcyB7zV6+gY06YgNdsr3oy/cXRh7fnC3Ph/VAu1EVg==", + "license": "MIT", + "peer": true, + "dependencies": { + "iterare": "1.2.1", + "object-hash": "3.0.0", + "tslib": "2.8.1" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0", + "@nestjs/platform-socket.io": "^11.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/platform-socket.io": { + "optional": true + } + } + }, + "node_modules/@node-ipc/js-queue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@node-ipc/js-queue/-/js-queue-2.0.3.tgz", + "integrity": "sha512-fL1wpr8hhD5gT2dA1qifeVaoDFlQR5es8tFuKqjHX+kdOtdNHnxkVZbtIrR2rxnMFvehkjaZRNV2H/gPXlb0hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "easy-stack": "1.0.1" + }, + "engines": { + "node": ">=1.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nuxt/opencollective": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.4.1.tgz", + "integrity": "sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==", + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + }, + "bin": { + "opencollective": "bin/opencollective.js" + }, + "engines": { + "node": "^14.18.0 || >=16.10.0", + "npm": ">=5.10.0" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.29", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz", + "integrity": "sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.1.tgz", + "integrity": "sha512-bxZtughE4VNVJlL1RdoSE545kc4JxL7op57KKoi59/gwuU5rV6jLWFXXc8jwgFoT6vtj+ZjO+Z2C5nrY0Cl6wA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.1.tgz", + "integrity": "sha512-44a1hreb02cAAfAKmZfXVercPFaDjqXCK+iKeVOlJ9ltvnO6QqsBHgKVPTu+MJHSLLeMEUbeG2qiDYgbFPU48g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.1.tgz", + "integrity": "sha512-usmzIgD0rf1syoOZ2WZvy8YpXK5G1V3btm3QZddoGSa6mOgfXWkkv+642bfUUldomgrbiLQGrPryb7DXLovPWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.1.tgz", + "integrity": "sha512-is3r/k4vig2Gt8mKtTlzzyaSQ+hd87kDxiN3uDSDwggJLUV56Umli6OoL+/YZa/KvtdrdyNfMKHzL/P4siOOmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.1.tgz", + "integrity": "sha512-QJ1ksgp/bDJkZB4daldVmHaEQkG4r8PUXitCOC2WRmRaSaHx5RwPoI3DHVfXKwDkB+Sk6auFI/+JHacTekPRSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.1.tgz", + "integrity": "sha512-J6ma5xgAzvqsnU6a0+jgGX/gvoGokqpkx6zY4cWizRrm0ffhHDpJKQgC8dtDb3+MqfZDIqs64REbfHDMzxLMqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.1.tgz", + "integrity": "sha512-JzWRR41o2U3/KMNKRuZNsDUAcAVUYhsPuMlx5RUldw0E4lvSIXFUwejtYz1HJXohUmqs/M6BBJAUBzKXZVddbg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.1.tgz", + "integrity": "sha512-L8kRIrnfMrEoHLHtHn+4uYA52fiLDEDyezgxZtGUTiII/yb04Krq+vk3P2Try+Vya9LeCE9ZHU8CXD6J9EhzHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.1.tgz", + "integrity": "sha512-ysAc0MFRV+WtQ8li8hi3EoFi7us6d1UzaS/+Dp7FYZfg3NdDljGMoVyiIp6Ucz7uhlYDBZ/zt6XI0YEZbUO11Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.1.tgz", + "integrity": "sha512-UV6l9MJpDbDZZ/fJvqNcvO1PcivGEf1AvKuTcHoLjVZVFeAMygnamCTDikCVMRnA+qJe+B3pSbgX2+lBMqgBhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.1.tgz", + "integrity": "sha512-UDUtelEprkA85g95Q+nj3Xf0M4hHa4DiJ+3P3h4BuGliY4NReYYqwlc0Y8ICLjN4+uIgCEvaygYlpf0hUj90Yg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.1.tgz", + "integrity": "sha512-vrRn+BYhEtNOte/zbc2wAUQReJXxEx2URfTol6OEfY2zFEUK92pkFBSXRylDM7aHi+YqEPJt9/ABYzmcrS4SgQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.1.tgz", + "integrity": "sha512-gto/1CxHyi4A7YqZZNznQYrVlPSaodOBPKM+6xcDSCMVZN/Fzb4K+AIkNz/1yAYz9h3Ng+e2fY9H6bgawVq17w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.1.tgz", + "integrity": "sha512-KZ6Vx7jAw3aLNjFR8eYVcQVdFa/cvBzDNRFM3z7XhNNunWjA03eUrEwJYPk0G8V7Gs08IThFKcAPS4WY/ybIrQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.1.tgz", + "integrity": "sha512-HvEixy2s/rWNgpwyKpXJcHmE7om1M89hxBTBi9Fs6zVuLU4gOrEMQNbNsN/tBVIMbLyysz/iwNiGtMOpLAOlvA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.1.tgz", + "integrity": "sha512-E/n8x2MSjAQgjj9IixO4UeEUeqXLtiA7pyoXCFYLuXpBA/t2hnbIdxHfA7kK9BFsYAoNU4st1rHYdldl8dTqGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.1.tgz", + "integrity": "sha512-IhJ087PbLOQXCN6Ui/3FUkI9pWNZe/Z7rEIVOzMsOs1/HSAECCvSZ7PkIbkNqL/AZn6WbZvnoVZw/qwqYMo4/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.1.tgz", + "integrity": "sha512-0++oPNgLJHBblreu0SFM7b3mAsBJBTY0Ksrmu9N6ZVrPiTkRgda52mWR7TKhHAsUb9noCjFvAw9l6ZO1yzaVbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.1.tgz", + "integrity": "sha512-VJXivz61c5uVdbmitLkDlbcTk9Or43YC2QVLRkqp86QoeFSqI81bNgjhttqhKNMKnQMWnecOCm7lZz4s+WLGpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.1.tgz", + "integrity": "sha512-NmZPVTUOitCXUH6erJDzTQ/jotYw4CnkMDjCYRxNHVD9bNyfrGoIse684F9okwzKCV4AIHRbUkeTBc9F2OOH5Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.1.tgz", + "integrity": "sha512-2SNj7COIdAf6yliSpLdLG8BEsp5lgzRehgfkP0Av8zKfQFKku6JcvbobvHASPJu4f3BFxej5g+HuQPvqPhHvpQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.1.tgz", + "integrity": "sha512-rLarc1Ofcs3DHtgSzFO31pZsCh8g05R2azN1q3fF+H423Co87My0R+tazOEvYVKXSLh8C4LerMK41/K7wlklcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@soda/friendly-errors-webpack-plugin": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.1.tgz", + "integrity": "sha512-h2ooWqP8XuFqTXT+NyAFbrArzfQA7R6HTezADrvD9Re8fxMLTPPniLdqVTdDaO0eIoLaAwKT+d6w+5GeTk7Vbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^3.0.0", + "error-stack-parser": "^2.0.6", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/@soda/get-current-script": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@soda/get-current-script/-/get-current-script-1.0.2.tgz", + "integrity": "sha512-T7VNNlYVM1SgQ+VsMYhnDkcGmWhQdL0bDyGm5TlQ3GBXnJscEClUUOKduWTmm2zCnvNLC1hc3JpuXjs/nFOc5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tokenizer/inflate": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "fflate": "^0.8.2", + "token-types": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@topoconfig/extends": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@topoconfig/extends/-/extends-0.16.2.tgz", + "integrity": "sha512-sTF+qpWakr5jf1Hn/kkFSi833xPW15s/loMAiKSYSSVv4vDonxf6hwCGzMXjLq+7HZoaK6BgaV72wXr1eY7FcQ==", + "dev": true, + "license": "MIT", + "bin": { + "xtends": "target/esm/cli.mjs" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "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/emscripten": { + "version": "1.41.5", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz", + "integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", + "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", + "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/passport": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz", + "integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-strategy": { + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz", + "integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/passport": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/validator": { + "version": "13.15.4", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.4.tgz", + "integrity": "sha512-LSFfpSnJJY9wbC0LQxgvfb+ynbHftFo0tMsFOl/J4wexLnYMmDSPaj2ZyDv3TkfL1UePxPrxOWJfbiRS8mQv7A==", + "license": "MIT" + }, + "node_modules/@types/w3c-web-usb": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.13.tgz", + "integrity": "sha512-N2nSl3Xsx8mRHZBvMSdNGtzMyeleTvtlEw+ujujgXalPqOjIA6UtrqcB6OzyUjkTbDm3J7P1RNK1lgoO7jxtsw==", + "license": "MIT" + }, + "node_modules/@types/webpack-env": { + "version": "1.18.8", + "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.18.8.tgz", + "integrity": "sha512-G9eAoJRMLjcvN4I08wB5I7YofOb/kaJNd5uoCMX+LbKXTPCF+ZIHuqTnFaK9Jz1rgs035f9JUPUhNFtqgucy/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz", + "integrity": "sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.29" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.8.tgz", + "integrity": "sha512-wQgmtW6FtPNn4lWUXi8ZSYLpOIb92j3QCujxX3sQ81NTfQ/ORnE0HtK7Kqf2+7J9jeveMGyGyc4NWc5qy3rC4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.0.8", + "ast-v8-to-istanbul": "^0.3.8", + "debug": "^4.4.3", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.0.8", + "vitest": "4.0.8" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.8.tgz", + "integrity": "sha512-Rv0eabdP/xjAHQGr8cjBm+NnLHNoL268lMDK85w2aAGLFoVKLd8QGnVon5lLtkXQCoYaNL0wg04EGnyKkkKhPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.8", + "@vitest/utils": "4.0.8", + "chai": "^6.2.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.8.tgz", + "integrity": "sha512-9FRM3MZCedXH3+pIh+ME5Up2NBBHDq0wqwhOKkN4VnvCiKbVxddqH9mSGPZeawjd12pCOGnl+lo/ZGHt0/dQSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.8", + "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.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.8.tgz", + "integrity": "sha512-qRrjdRkINi9DaZHAimV+8ia9Gq6LeGz2CgIEmMLz3sBDYV53EsnLZbJMR1q84z1HZCMsf7s0orDgZn7ScXsZKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.8.tgz", + "integrity": "sha512-mdY8Sf1gsM8hKJUQfiPT3pn1n8RF4QBcJYFslgWh41JTfrK1cbqY8whpGCFzBl45LN028g0njLCYm0d7XxSaQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.8", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.8.tgz", + "integrity": "sha512-Nar9OTU03KGiubrIOFhcfHg8FYaRaNT+bh5VUlNz8stFhCZPNrJvmZkhsr1jtaYvuefYFwK2Hwrq026u4uPWCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.8", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.8.tgz", + "integrity": "sha512-nvGVqUunyCgZH7kmo+Ord4WgZ7lN0sOULYXUOYuHr55dvg9YvMz3izfB189Pgp28w0vWFbEEfNc/c3VTrqrXeA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.8.tgz", + "integrity": "sha512-pdk2phO5NDvEFfUTxcTP8RFYjVj/kfLSPIN5ebP2Mu9kcIMeAQTbknqcFEyBcC4z2pJlJI9aS5UQjcYfhmKAow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.8", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.23.tgz", + "integrity": "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.23" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.23.tgz", + "integrity": "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.23.tgz", + "integrity": "sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.23", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/cli-overlay": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@vue/cli-overlay/-/cli-overlay-5.0.9.tgz", + "integrity": "sha512-aBdZWrYKxLuFz1FDsk/muFD7GycrsW73Gi11yRc7R2W7Bm8mDRc9HKAI790gdg4NV+chkDFmfkegjg5iMDEpAA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/cli-plugin-router": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@vue/cli-plugin-router/-/cli-plugin-router-5.0.9.tgz", + "integrity": "sha512-kopbO/8kIl5CAffwgptXEwV509i+M0FfwW4sSkgQ2RzpxOYBjQZvp+096mjZfFcWKSmryNP/ri/Mnu78vmhlhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/cli-shared-utils": "^5.0.9" + }, + "peerDependencies": { + "@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0-0" + } + }, + "node_modules/@vue/cli-plugin-typescript": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@vue/cli-plugin-typescript/-/cli-plugin-typescript-5.0.9.tgz", + "integrity": "sha512-68S9rtpLMZLOIjQ9UaLSPupiJlE6ySO68kDVraIkqeQNi0qfcnVOlXTsDd4UnobKv+v+qHnt593+8bt3mjXiyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.12.16", + "@types/webpack-env": "^1.15.2", + "@vue/cli-shared-utils": "^5.0.9", + "babel-loader": "^8.2.2", + "fork-ts-checker-webpack-plugin": "^6.4.0", + "globby": "^11.0.2", + "thread-loader": "^3.0.0", + "ts-loader": "^9.2.5", + "webpack": "^5.54.0" + }, + "peerDependencies": { + "@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0-0", + "cache-loader": "^4.1.0", + "typescript": ">=2", + "vue": "^2 || ^3.2.13", + "vue-template-compiler": "^2.0.0" + }, + "peerDependenciesMeta": { + "cache-loader": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/@vue/cli-plugin-vuex": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.9.tgz", + "integrity": "sha512-AQhgGNFVd4Pu2crvS0a+hRckgrJv07gzOASdbLd3I72wkT43dd01MLRp8IBRRsu92t3MXenW86AZUCbQBz3//A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0-0" + } + }, + "node_modules/@vue/cli-service": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@vue/cli-service/-/cli-service-5.0.9.tgz", + "integrity": "sha512-yTX7GVyM19tEbd+y5/gA6MkVKA6K61nVYHYAivD61Hx6odVFmQsaC3/R3cWAHM1P5oVKCevBbumPljbT+tFG2w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.12.16", + "@soda/friendly-errors-webpack-plugin": "^1.8.0", + "@soda/get-current-script": "^1.0.2", + "@types/minimist": "^1.2.0", + "@vue/cli-overlay": "^5.0.9", + "@vue/cli-plugin-router": "^5.0.9", + "@vue/cli-plugin-vuex": "^5.0.9", + "@vue/cli-shared-utils": "^5.0.9", + "@vue/component-compiler-utils": "^3.3.0", + "@vue/vue-loader-v15": "npm:vue-loader@^15.9.7", + "@vue/web-component-wrapper": "^1.3.0", + "acorn": "^8.0.5", + "acorn-walk": "^8.0.2", + "address": "^1.1.2", + "autoprefixer": "^10.2.4", + "browserslist": "^4.16.3", + "case-sensitive-paths-webpack-plugin": "^2.3.0", + "cli-highlight": "^2.1.10", + "clipboardy": "^2.3.0", + "cliui": "^7.0.4", + "copy-webpack-plugin": "^9.0.1", + "css-loader": "^6.5.0", + "css-minimizer-webpack-plugin": "^3.0.2", + "cssnano": "^5.0.0", + "debug": "^4.1.1", + "default-gateway": "^6.0.3", + "dotenv": "^10.0.0", + "dotenv-expand": "^5.1.0", + "fs-extra": "^9.1.0", + "globby": "^11.0.2", + "hash-sum": "^2.0.0", + "html-webpack-plugin": "^5.1.0", + "is-file-esm": "^1.0.0", + "launch-editor-middleware": "^2.2.1", + "lodash.defaultsdeep": "^4.6.1", + "lodash.mapvalues": "^4.6.0", + "mini-css-extract-plugin": "^2.5.3", + "minimist": "^1.2.5", + "module-alias": "^2.2.2", + "portfinder": "^1.0.26", + "postcss": "^8.2.6", + "postcss-loader": "^6.1.1", + "progress-webpack-plugin": "^1.0.12", + "ssri": "^8.0.1", + "terser-webpack-plugin": "^5.1.1", + "thread-loader": "^3.0.0", + "vue-loader": "^17.0.0", + "vue-style-loader": "^4.1.3", + "webpack": "^5.54.0", + "webpack-bundle-analyzer": "^4.4.0", + "webpack-chain": "^6.5.1", + "webpack-dev-server": "^4.7.3", + "webpack-merge": "^5.7.3", + "webpack-virtual-modules": "^0.4.2", + "whatwg-fetch": "^3.6.2" + }, + "bin": { + "vue-cli-service": "bin/vue-cli-service.js" + }, + "engines": { + "node": "^12.0.0 || >= 14.0.0" + }, + "peerDependencies": { + "vue-template-compiler": "^2.0.0", + "webpack-sources": "*" + }, + "peerDependenciesMeta": { + "cache-loader": { + "optional": true + }, + "less-loader": { + "optional": true + }, + "pug-plain-loader": { + "optional": true + }, + "raw-loader": { + "optional": true + }, + "sass-loader": { + "optional": true + }, + "stylus-loader": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/@vue/cli-service/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/@vue/cli-service/node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@vue/cli-service/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vue/cli-shared-utils": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-5.0.9.tgz", + "integrity": "sha512-lf4KykiG8j9KwvNVi7fKtASmHuLsxCcCsflVU2b2CHMRuR4weOIV3zuuCrjWKjk0APn/MHJhgCjJGzHMbTtd5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@achrinza/node-ipc": "^9.2.5", + "chalk": "^4.1.2", + "execa": "^1.0.0", + "joi": "^17.4.0", + "launch-editor": "^2.2.1", + "lru-cache": "^6.0.0", + "node-fetch": "^2.6.7", + "open": "^8.0.2", + "ora": "^5.3.0", + "read-pkg": "^5.1.1", + "semver": "^7.3.4", + "strip-ansi": "^6.0.0" + } + }, + "node_modules/@vue/cli-shared-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@vue/cli-shared-utils/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vue/cli-shared-utils/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.24.tgz", + "integrity": "sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.24", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.24.tgz", + "integrity": "sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.24", + "@vue/shared": "3.5.24" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.24.tgz", + "integrity": "sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/compiler-core": "3.5.24", + "@vue/compiler-dom": "3.5.24", + "@vue/compiler-ssr": "3.5.24", + "@vue/shared": "3.5.24", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.24.tgz", + "integrity": "sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.24", + "@vue/shared": "3.5.24" + } + }, + "node_modules/@vue/component-compiler-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz", + "integrity": "sha512-97sfH2mYNU+2PzGrmK2haqffDpVASuib9/w2/noxiFi31Z54hW+q3izKQXXQZSNhtiUpAI36uSuYepeBe4wpHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "consolidate": "^0.15.1", + "hash-sum": "^1.0.2", + "lru-cache": "^4.1.2", + "merge-source-map": "^1.1.0", + "postcss": "^7.0.36", + "postcss-selector-parser": "^6.0.2", + "source-map": "~0.6.1", + "vue-template-es2015-compiler": "^1.9.0" + }, + "optionalDependencies": { + "prettier": "^1.18.2 || ^2.0.0" + } + }, + "node_modules/@vue/component-compiler-utils/node_modules/hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/component-compiler-utils/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "license": "ISC", + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/@vue/component-compiler-utils/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vue/component-compiler-utils/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/@vue/component-compiler-utils/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vue/language-core": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.1.3.tgz", + "integrity": "sha512-KpR1F/eGAG9D1RZ0/T6zWJs6dh/pRLfY5WupecyYKJ1fjVmDMgTPw9wXmKv2rBjo4zCJiOSiyB8BDP1OUwpMEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.23", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.0.0", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.2" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/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", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz", + "integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.24" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.24.tgz", + "integrity": "sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.24", + "@vue/shared": "3.5.24" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz", + "integrity": "sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.24", + "@vue/runtime-core": "3.5.24", + "@vue/shared": "3.5.24", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.24.tgz", + "integrity": "sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.24", + "@vue/shared": "3.5.24" + }, + "peerDependencies": { + "vue": "3.5.24" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.24.tgz", + "integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==", + "license": "MIT" + }, + "node_modules/@vue/test-utils": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz", + "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^2.0.0" + } + }, + "node_modules/@vue/vue-loader-v15": { + "name": "vue-loader", + "version": "15.11.1", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.11.1.tgz", + "integrity": "sha512-0iw4VchYLePqJfJu9s62ACWUXeSqM30SQqlIftbYWM3C+jpPcEHKSPUZBLjSF9au4HTHQ/naF6OGnO3Q/qGR3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/component-compiler-utils": "^3.1.0", + "hash-sum": "^1.0.2", + "loader-utils": "^1.1.0", + "vue-hot-reload-api": "^2.3.0", + "vue-style-loader": "^4.1.0" + }, + "peerDependencies": { + "css-loader": "*", + "webpack": "^3.0.0 || ^4.1.0 || ^5.0.0-0" + }, + "peerDependenciesMeta": { + "cache-loader": { + "optional": true + }, + "prettier": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/@vue/vue-loader-v15/node_modules/hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/web-component-wrapper": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vue/web-component-wrapper/-/web-component-wrapper-1.3.0.tgz", + "integrity": "sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@websdr/core": { + "resolved": "packages/core", + "link": true + }, + "node_modules/@websdr/frontend-core": { + "resolved": "packages/frontend-core", + "link": true + }, + "node_modules/@websdr/nestjs-microservice": { + "resolved": "packages/nestjs-microservice", + "link": true + }, + "node_modules/@websdr/vue3-components": { + "resolved": "packages/vue3-components", + "link": true + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/alien-signals": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.0.tgz", + "integrity": "sha512-yufC6VpSy8tK3I0lO67pjumo5JvDQVQyr38+3OHqe6CHl1t2VZekKZ7EKKZSqk0cRmE7U7tfZbpXiKNzuc+ckg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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.8", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.8.tgz", + "integrity": "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-loader": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.4.1.tgz", + "integrity": "sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.4", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/babel-loader/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/babel-loader/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.25", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.25.tgz", + "integrity": "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001754", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", + "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/case-sensitive-paths-webpack-plugin": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", + "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/chai": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.0.tgz", + "integrity": "sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT", + "peer": true + }, + "node_modules/class-validator": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz", + "integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.11.1", + "validator": "^13.9.0" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", + "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "arch": "^2.1.1", + "execa": "^1.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/consolidate": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", + "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", + "deprecated": "Please upgrade to consolidate v1.0.0+ as it has been modernized with several long-awaited fixes implemented. Maintenance is supported by Forward Email at https://forwardemail.net ; follow/watch https://github.com/ladjs/consolidate for updates and release changelog", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "^3.1.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-webpack-plugin": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-9.1.0.tgz", + "integrity": "sha512-rxnR7PaGigJzhqETHGmAcxKnLZSR5u1Y3/bcIv/1FnqXedcL/E2ewK7ZCNrArJKCiSv8yVXhTqetJh8inDvfsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.7", + "glob-parent": "^6.0.1", + "globby": "^11.0.3", + "normalize-path": "^3.0.0", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/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/copy-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/cross-spawn/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/css-declaration-sorter": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", + "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", + "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano": "^5.0.6", + "jest-worker": "^27.0.2", + "postcss": "^8.3.5", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", + "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-preset-default": "^5.2.14", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "version": "5.2.14", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", + "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-declaration-sorter": "^6.3.1", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.1", + "postcss-convert-values": "^5.1.3", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.7", + "postcss-merge-rules": "^5.1.4", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.4", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.1", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.2", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/default-gateway/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/default-gateway/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/default-gateway/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/default-gateway/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/default-gateway/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/default-gateway/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/default-gateway/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/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/depseek": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/depseek/-/depseek-0.4.3.tgz", + "integrity": "sha512-0Ex/Uoz9Y9oipbqvgSc7FoV2q4GTDGORkg8TGgM9Pd3RyvRzrDYScKplWiqHU7WqckbKN5kukqD0opEayu2bXg==", + "dev": true, + "license": "MIT" + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.1.tgz", + "integrity": "sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==", + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/easy-stack": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz", + "integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.249", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.249.tgz", + "integrity": "sha512-5vcfL3BBe++qZ5kuFhD/p8WOM1N9m3nwvJPULJx+4xf2usSlZFJ0qoNYO2fOX4hi3ocuDcmDobtA+5SFr4OmBg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/enhanced-resolve/node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "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/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-pubsub": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz", + "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==", + "dev": true, + "license": "Unlicense", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" + }, + "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-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "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-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/file-type": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.0.0.tgz", + "integrity": "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==", + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.2.7", + "strtok3": "^10.2.2", + "token-types": "^6.0.0", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-cache-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/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/foreground-child/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/foreground-child/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/foreground-child/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/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/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/fork-ts-checker-webpack-plugin": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", + "integrity": "sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.8.3", + "@types/json-schema": "^7.0.5", + "chalk": "^4.1.0", + "chokidar": "^3.4.2", + "cosmiconfig": "^6.0.0", + "deepmerge": "^4.2.2", + "fs-extra": "^9.0.0", + "glob": "^7.1.6", + "memfs": "^3.1.2", + "minimatch": "^3.0.4", + "schema-utils": "2.7.0", + "semver": "^7.3.2", + "tapable": "^1.0.0" + }, + "engines": { + "node": ">=10", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "eslint": ">= 6", + "typescript": ">= 2.7", + "vue-template-compiler": "*", + "webpack": ">= 4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-monkey": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/happy-dom": { + "version": "20.0.10", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.0.10.tgz", + "integrity": "sha512-6umCCHcjQrhP5oXhrHQQvLB0bwb1UzHAHdsXy+FjtKoYjUhmNZsQL8NivwM1vDvNEChJabVrUYxUnp/ZdYmy2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "whatwg-mimetype": "^3.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/happy-dom/node_modules/@types/node": { + "version": "20.19.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", + "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/happy-dom/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "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/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", + "dev": true, + "license": "MIT" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true, + "license": "ISC" + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "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/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.4.tgz", + "integrity": "sha512-V/PZeWsqhfpE27nKeX9EO2sbR+D17A+tLf6qU+ht66jdUsN0QLKJN27Z+1+gHrVMKgndBahes0PU6rRihDgHTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/html-webpack-plugin/node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "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/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-file-esm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-file-esm/-/is-file-esm-1.0.0.tgz", + "integrity": "sha512-rZlaNKb4Mr8WlRu2A9XdeoKgnO5aA53XdPHgCKVyCrQ/rWi89RET1+bq37Ru46obaQXeiX4vmFIm1vks41hoSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "read-pkg-up": "^7.0.1" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "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/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.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/iterare": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", + "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", + "license": "ISC", + "engines": { + "node": ">=6" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/javascript-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", + "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-beautify/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/js-beautify/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/js-message": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz", + "integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "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/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/launch-editor": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", + "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" + } + }, + "node_modules/launch-editor-middleware": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/launch-editor-middleware/-/launch-editor-middleware-2.12.0.tgz", + "integrity": "sha512-SgU5QWoR+Grq1sQedvS/RlfoyO6bdvrztpP+2RRg8UzE7Jz2Yup5J4jiFfm2J9dYBCQYD26AbJVbjnvgwdL6Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "launch-editor": "^2.12.0" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.12.26", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.26.tgz", + "integrity": "sha512-MagMOuqEXB2Pa90cWE+BoCmcKJx+de5uBIicaUkQ+uiEslZ0OBMNOkSZT/36syXNHu68UeayTxPm3DYM2IHoLQ==", + "license": "MIT" + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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-esm": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.3.tgz", + "integrity": "sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + }, + { + "type": "buymeacoffee", + "url": "https://buymeacoffee.com/borewit" + } + ], + "license": "MIT", + "engines": { + "node": ">=13.2.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/loader-utils/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.defaultsdeep": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz", + "integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.mapvalues": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz", + "integrity": "sha512-JPFqXFeZQ7BfS00H58kClY7SPVeHertPE0lNuCyZ26/XlN8TvakYD7b9bGyNmXbT/D3BbtPAAmq90gPWqLkxlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-update": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", + "integrity": "sha512-vlP11XfFGyeNQlmEn9tJ66rEW1coA/79m5z6BCkudjbAGE83uhAcGYrBFwfs3AdLiLzGRusRPAbSPK9xZteCmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^3.0.0", + "cli-cursor": "^2.0.0", + "wrap-ansi": "^3.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", + "integrity": "sha512-iXR3tDXpbnTpzjKSylUJRkLuOrEC7hwEB221cgn6wtF8wpmz28puFXAEfPT5zrjM3wahygB//VuWEr1vTkDcNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "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/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz", + "integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/module-alias": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.3.tgz", + "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mylas": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.14.tgz", + "integrity": "sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } + }, + "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==", + "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/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "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/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "license": "MIT", + "dependencies": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "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-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "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/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/plimit-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "queue-lit": "^1.5.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/portfinder": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz", + "integrity": "sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.6", + "debug": "^4.3.6" + }, + "engines": { + "node": ">= 10.12" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-colormin": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", + "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-convert-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", + "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-loader": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", + "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-loader/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postcss-loader/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", + "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", + "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", + "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", + "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "dev": true, + "license": "MIT", + "dependencies": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", + "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/progress-webpack-plugin": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/progress-webpack-plugin/-/progress-webpack-plugin-1.0.16.tgz", + "integrity": "sha512-sdiHuuKOzELcBANHfrupYo+r99iPRyOnw15qX+rNlVUqXGfjXdH4IgxriKwG1kNJwVswKQHMdj1hYZMcb9jFaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.1.0", + "figures": "^2.0.0", + "log-update": "^2.3.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "peerDependencies": { + "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/progress-webpack-plugin/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/progress-webpack-plugin/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/progress-webpack-plugin/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/progress-webpack-plugin/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/progress-webpack-plugin/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/progress-webpack-plugin/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "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/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-lit": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.1.tgz", + "integrity": "sha512-n2I0V0lN3E9cxxMqBCT3opWOiQBzRN7UG60z/WDKqdX2zHUS/39lezBcsckZFsV6fUTSnfqI7kHf60jDAPGKug==", + "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.53.1", + "@rollup/rollup-android-arm64": "4.53.1", + "@rollup/rollup-darwin-arm64": "4.53.1", + "@rollup/rollup-darwin-x64": "4.53.1", + "@rollup/rollup-freebsd-arm64": "4.53.1", + "@rollup/rollup-freebsd-x64": "4.53.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.1", + "@rollup/rollup-linux-arm-musleabihf": "4.53.1", + "@rollup/rollup-linux-arm64-gnu": "4.53.1", + "@rollup/rollup-linux-arm64-musl": "4.53.1", + "@rollup/rollup-linux-loong64-gnu": "4.53.1", + "@rollup/rollup-linux-ppc64-gnu": "4.53.1", + "@rollup/rollup-linux-riscv64-gnu": "4.53.1", + "@rollup/rollup-linux-riscv64-musl": "4.53.1", + "@rollup/rollup-linux-s390x-gnu": "4.53.1", + "@rollup/rollup-linux-x64-gnu": "4.53.1", + "@rollup/rollup-linux-x64-musl": "4.53.1", + "@rollup/rollup-openharmony-arm64": "4.53.1", + "@rollup/rollup-win32-arm64-msvc": "4.53.1", + "@rollup/rollup-win32-ia32-msvc": "4.53.1", + "@rollup/rollup-win32-x64-gnu": "4.53.1", + "@rollup/rollup-win32-x64-msvc": "4.53.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.93.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.3.tgz", + "integrity": "sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass/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/sass/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/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", + "dev": true, + "license": "MIT" + }, + "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/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strtok3": { + "version": "10.3.4", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", + "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/stylehacks": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", + "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "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/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "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/thread-loader": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/thread-loader/-/thread-loader-3.0.4.tgz", + "integrity": "sha512-ByaL2TPb+m6yArpqQUZvP+5S1mZtXsEP7nWKKlAUTm7fCml8kB5s1uI3+eHRP2bk5mVYfRSBI7FFf+tWEyLZwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^4.1.0", + "loader-utils": "^2.0.0", + "neo-async": "^2.6.2", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.27.0 || ^5.0.0" + } + }, + "node_modules/thread-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/thread-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "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/tinyglobby/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/tinyglobby/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/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/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/token-types": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", + "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.1.0", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ts-loader": { + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", + "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ts-loader/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/tsc-alias": { + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz", + "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "get-tsconfig": "^4.10.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" + }, + "engines": { + "node": ">=16.20.2" + } + }, + "node_modules/tsc-esm-fix": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/tsc-esm-fix/-/tsc-esm-fix-3.1.2.tgz", + "integrity": "sha512-1/OpZssMcEp2ae6DyZV+yvDviofuCdDf7dEWEaBvm/ac8vtS04lFyl0LVs8LQE56vjKHytgzVjPIL9udM4QuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@topoconfig/extends": "^0.16.2", + "depseek": "^0.4.1", + "fast-glob": "^3.3.2", + "fs-extra": "^11.2.0", + "json5": "^2.2.3", + "type-flag": "^3.0.0" + }, + "bin": { + "tsc-esm-fix": "target/esm/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/type-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/type-flag/-/type-flag-3.0.0.tgz", + "integrity": "sha512-3YaYwMseXCAhBB14RXW5cRQfJQlEknS6i4C8fCfeUdS3ihG9EdccdR9kt3vP73ZdeTGmPb4bZtkDn5XMIn1DLA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/type-flag?sponsor=1" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "license": "MIT", + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "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/usb": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/usb/-/usb-2.16.0.tgz", + "integrity": "sha512-jD88fvzDViMDH5KmmNJgzMBDj/95bDTt6+kBNaNxP4G98xUTnDMiLUY2CYmToba6JAFhM9VkcaQuxCNRLGR7zg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@types/w3c-web-usb": "^1.0.6", + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.5.0" + }, + "engines": { + "node": ">=12.22.0 <13.0 || >=14.17.0" + } + }, + "node_modules/usb/node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validator": { + "version": "13.15.22", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.22.tgz", + "integrity": "sha512-uT/YQjiyLJP7HSrv/dPZqK9L28xf8hsNca01HSz1dfmI0DgMfjopp1rO/z13NeGF1tVystF0Ejx3y4rUKPw+bQ==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz", + "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.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/vite/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/vite/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/vitest": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.8.tgz", + "integrity": "sha512-urzu3NCEV0Qa0Y2PwvBtRgmNtxhj5t5ULw7cuKhIHh3OrkKTLlut0lnBOv9qe5OvbkMH2g38G7KPDCTpIytBVg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/expect": "4.0.8", + "@vitest/mocker": "4.0.8", + "@vitest/pretty-format": "4.0.8", + "@vitest/runner": "4.0.8", + "@vitest/snapshot": "4.0.8", + "@vitest/spy": "4.0.8", + "@vitest/utils": "4.0.8", + "debug": "^4.4.3", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.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": "*", + "@types/debug": "^4.1.12", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.8", + "@vitest/browser-preview": "4.0.8", + "@vitest/browser-webdriverio": "4.0.8", + "@vitest/ui": "4.0.8", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "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/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.24.tgz", + "integrity": "sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.5.24", + "@vue/compiler-sfc": "3.5.24", + "@vue/runtime-dom": "3.5.24", + "@vue/server-renderer": "3.5.24", + "@vue/shared": "3.5.24" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.2.12.tgz", + "integrity": "sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue-hot-reload-api": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz", + "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue-loader": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.4.2.tgz", + "integrity": "sha512-yTKOA4R/VN4jqjw4y5HrynFL8AK0Z3/Jt7eOJXEitsm0GMRHDBjCfCiuTiLP7OESvsZYo2pATCWhDqxC5ZrM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "watchpack": "^2.4.0" + }, + "peerDependencies": { + "webpack": "^4.1.0 || ^5.0.0-0" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/vue-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/vue-style-loader": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", + "integrity": "sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash-sum": "^1.0.2", + "loader-utils": "^1.0.2" + } + }, + "node_modules/vue-style-loader/node_modules/hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue-template-es2015-compiler": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", + "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue-tsc": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.1.3.tgz", + "integrity": "sha512-StMNfZHwPIXQgY3KxPKM0Jsoc8b46mDV3Fn2UlHCBIwRJApjqrSwqeMYgWf0zpN+g857y74pv7GWuBm+UqQe1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.23", + "@vue/language-core": "3.1.3" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/webpack": { + "version": "5.102.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.1.tgz", + "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.26.3", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.3", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.4", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-bundle-analyzer/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/webpack-bundle-analyzer/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-chain": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/webpack-chain/-/webpack-chain-6.5.1.tgz", + "integrity": "sha512-7doO/SRtLu8q5WM0s7vPKPWX580qhi0/yBHkOxNkv50f6qB76Zy9o2wRTrrPULqYTvQlVHuvbA8v+G5ayuUDsA==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "deepmerge": "^1.5.2", + "javascript-stringify": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-chain/node_modules/deepmerge": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz", + "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack-dev-middleware/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.4", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-server/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.4.6.tgz", + "integrity": "sha512-5tyDlKLqPfMqjT3Q9TAqf2YqjwmnUleZwzJi1A5qXnlBCdj2AtOJ6wAWdglTIDOPgOiOrXeBeFcsQ8+aGQ6QbA==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack/node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "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/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "packages/core": { + "name": "@websdr/core", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@types/node": "^24.10.0" + } + }, + "packages/frontend-core": { + "name": "@websdr/frontend-core", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@websdr/core": "^1.0.0", + "usb": "^2.16.0" + }, + "devDependencies": { + "@types/emscripten": "^1.41.5", + "@types/node": "^24.10.0", + "@types/w3c-web-usb": "^1.0.13" + } + }, + "packages/nestjs-microservice": { + "name": "@websdr/nestjs-microservice", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@nestjs/common": "^11.1.8", + "@nestjs/config": "^4.0.2", + "@nestjs/jwt": "^11.0.1", + "@nestjs/microservices": "^11.1.8", + "@nestjs/passport": "^11.0.5", + "@nestjs/testing": "^11.1.8", + "@nestjs/websockets": "^11.1.8", + "@websdr/core": "^1.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.2", + "passport-jwt": "^4.0.1", + "ws": "^8.18.3" + }, + "devDependencies": { + "@types/passport-jwt": "^4.0.1", + "@types/ws": "^8.18.1" + } + }, + "packages/vue3-components": { + "name": "@websdr/vue3-components", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@websdr/core": "^1.0.0", + "@websdr/frontend-core": "^1.0.0", + "vue": "^3.5.23" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.1", + "@vue/cli-plugin-typescript": "^5.0.9", + "@vue/cli-service": "^5.0.9", + "@vue/test-utils": "^2.4.6", + "happy-dom": "^20.0.10", + "sass": "^1.93.3", + "vue-tsc": "^3.1.3" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ee3f379 --- /dev/null +++ b/package.json @@ -0,0 +1,36 @@ +{ + "name": "websdr", + "version": "1.0.0", + "description": "WebSDR package", + "author": "Timur ", + "license": "MIT", + "keywords": [ + "websdr", + "sdr", + "wsdr", + "wavelet" + ], + "type": "module", + "workspaces": [ + "./packages/core", + "./packages/nestjs-microservice", + "./packages/frontend-core", + "./packages/vue3-components" + ], + "scripts": { + "build": "npm run build --workspaces", + "test": "npm run test --workspaces", + "test:all": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "test:ui": "vitest --ui" + }, + "devDependencies": { + "@vitest/coverage-v8": "^4.0.7", + "tsc-alias": "^1.8.16", + "tsc-esm-fix": "^3.1.2", + "typescript": "^5.9.3", + "vite": "^7.2.1", + "vitest": "^4.0.7" + } +} \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..26f7994 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,42 @@ +{ + "name": "@websdr/core", + "version": "1.0.0", + "description": "This is the core package for WebSDR", + "author": "Timur ", + "license": "MIT", + "keywords": [], + "main": "dist/index.js", + "types": "dist/index.d.ts", + "type": "module", + "scripts": { + "build": "tsc && tsc-alias && tsc-esm-fix", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" + }, + "devDependencies": { + "@types/node": "^24.10.0" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.js" + }, + "./common": { + "types": "./dist/common/index.d.ts", + "import": "./dist/common/index.js", + "require": "./dist/common/index.js" + }, + "./transform": { + "types": "./dist/transform/index.d.ts", + "import": "./dist/transform/index.js", + "require": "./dist/transform/index.js" + }, + "./utils": { + "types": "./dist/utils/index.d.ts", + "import": "./dist/utils/index.js", + "require": "./dist/utils/index.js" + } + } +} \ No newline at end of file diff --git a/packages/core/src/common/defines.ts b/packages/core/src/common/defines.ts new file mode 100644 index 0000000..bd68f5f --- /dev/null +++ b/packages/core/src/common/defines.ts @@ -0,0 +1,6 @@ +// Stream parameters +export const CHUNK_SIZE = 8192; +export const FLOAT_SIZE = Float32Array.BYTES_PER_ELEMENT; +export const COMPLEX_FLOAT_SIZE = 2 * FLOAT_SIZE; +export const INT16_SIZE = Int16Array.BYTES_PER_ELEMENT; +export const COMPLEX_INT16_SIZE = 2 * INT16_SIZE; \ No newline at end of file diff --git a/packages/core/src/common/index.ts b/packages/core/src/common/index.ts new file mode 100644 index 0000000..4bad98d --- /dev/null +++ b/packages/core/src/common/index.ts @@ -0,0 +1,5 @@ +// Re-exporting all common types and definitions +export { DataType } from './types'; +export { + CHUNK_SIZE, FLOAT_SIZE, COMPLEX_FLOAT_SIZE, INT16_SIZE, COMPLEX_INT16_SIZE +} from './defines'; \ No newline at end of file diff --git a/packages/core/src/common/types.ts b/packages/core/src/common/types.ts new file mode 100644 index 0000000..710bf37 --- /dev/null +++ b/packages/core/src/common/types.ts @@ -0,0 +1,7 @@ +export enum DataType { + cf32 = 'cf32', + ci16 = 'ci16', + f32 = 'f32', + i16 = 'i16', + i8 = 'i8', +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 0000000..e49dba5 --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,3 @@ +// Re-export all core functionalities +export * from './common'; +export * from './utils'; diff --git a/packages/core/src/tests/transform/converters.test.ts b/packages/core/src/tests/transform/converters.test.ts new file mode 100644 index 0000000..dd65b9f --- /dev/null +++ b/packages/core/src/tests/transform/converters.test.ts @@ -0,0 +1,97 @@ +import { describe, it, expect } from 'vitest'; +import { bufferI16ToF32, bufferF32ToI16, clipF32Buffer } from '@/transform/converters'; + +describe('converters', () => { + it('bufferI16ToF32 converts i16 extremes and zero', () => { + const inArr = new Int16Array([0, 32767, -32768]); + const out = bufferI16ToF32(inArr); + expect(out[0]).toBeCloseTo(0, 7); + expect(out[1]).toBeCloseTo(32767 / 32768, 7); + expect(out[2]).toBeCloseTo(-1, 7); + }); + + it('bufferI16ToF32 respects provided output buffer length', () => { + const inArr = new Int16Array([0, 32767, -32768]); + const outBuf = new Float32Array(2); // shorter + const out = bufferI16ToF32(inArr, outBuf); + expect(out.length).toBe(2); + expect(out[0]).toBeCloseTo(0, 7); + expect(out[1]).toBeCloseTo(32767 / 32768, 7); + }); + + it('bufferF32ToI16 converts floats and clamps/rounds correctly', () => { + const inArr = new Float32Array([0, 1, -1, 0.5, -0.5, 2, -2, NaN]); + const out = bufferF32ToI16(inArr); + expect(out[0]).toBe(0); + expect(out[1]).toBe(32767); // +1 -> 32767 + expect(out[2]).toBe(-32767); // -1 -> -32767 + expect(out[3]).toBe(16383); // 0.5 -> 16383 + expect(out[4]).toBe(-16383); // -0.5 -> -16383 + expect(out[5]).not.toBe(32767); // >1 -> clipped - at this moment no clipping logic because performance is priority + expect(out[6]).not.toBe(-32768); // < -1 -> clipped - at this moment no clipping logic because performance is priority + expect(out[7]).toBe(0); // NaN -> 0 + }); + + it('bufferF32ToI16 respects provided output buffer length', () => { + const inArr = new Float32Array([0.1, 0.2, 0.3]); + const outBuf = new Int16Array(2); // shorter + const out = bufferF32ToI16(inArr, outBuf); + expect(out.length).toBe(2); + expect(out[0]).toBe(3276); // Math.round(0.1 * 32767) + expect(out[1]).toBe(6553); // Math.round(0.2 * 32767) + }); +}); + +describe('clipF32Buffer', () => { + it('clips values in place when outbuf not provided', async () => { + const buffer = new Float32Array([-2, -1, -0.5, 0, 0.5, 1, 2, NaN]); + clipF32Buffer(buffer); + expect(buffer[0]).toBe(-1); // -2 -> -1 + expect(buffer[1]).toBe(-1); // -1 stays -1 + expect(buffer[2]).toBeCloseTo(-0.5, 7); // in-range + expect(buffer[3]).toBeCloseTo(0, 7); // in-range + expect(buffer[4]).toBeCloseTo(0.5, 7); // in-range + expect(buffer[5]).toBe(1); // 1 stays 1 + expect(buffer[6]).toBe(1); // 2 -> 1 + expect(Number.isNaN(buffer[7])).toBeTruthy(); // NaN remains NaN + }); + + it('writes to provided outbuf and does not modify input (current behavior: only clipped values are written)', async () => { + const input = new Float32Array([2, -2, 0.5]); + const outbuf = new Float32Array(input.length); + // fill outbuf with sentinel to ensure in-range values are left untouched by the function + outbuf.fill(0); + clipF32Buffer(input, outbuf); + // current implementation only writes clipped values to outbuf, leaves in-range values as-is + expect(outbuf[0]).toBe(1); // 2 -> 1 + expect(outbuf[1]).toBe(-1); // -2 -> -1 + expect(outbuf[2]).toBe(0); // in-range 0.5 is NOT copied to outbuf (remains sentinel) + // input must remain unchanged + expect(input[0]).toBe(2); + expect(input[1]).toBe(-2); + expect(input[2]).toBeCloseTo(0.5, 7); + }); + + it('provided outbuf keeps its values for in-range and NaN inputs (documenting edge behavior)', async () => { + const sentinel = 123.456; + const input = new Float32Array([0.5, NaN, -0.5]); + const outbuf = new Float32Array(input.length); + outbuf.fill(sentinel); + clipF32Buffer(input, outbuf); + // in-range values and NaN are not written into outbuf by current implementation + expect(outbuf[0]).toBeCloseTo(sentinel); // 0.5 left as sentinel + expect(outbuf[1]).toBeCloseTo(sentinel); // NaN left as sentinel + expect(outbuf[2]).toBeCloseTo(sentinel); // -0.5 left as sentinel + // original input unchanged + expect(Number.isNaN(input[1])).toBeTruthy(); + expect(input[0]).toBeCloseTo(0.5, 7); + expect(input[2]).toBeCloseTo(-0.5, 7); + }); + + it('works when outbuf is the same buffer and leaves in-range values unchanged', async () => { + const buffer = new Float32Array([-0.2, 0.2]); + clipF32Buffer(buffer, buffer); + expect(buffer[0]).toBeCloseTo(-0.2, 7); + expect(buffer[1]).toBeCloseTo(0.2, 7); + }); +}); \ No newline at end of file diff --git a/packages/core/src/tests/utils/circularbuffer.test.ts b/packages/core/src/tests/utils/circularbuffer.test.ts new file mode 100644 index 0000000..1ea79ea --- /dev/null +++ b/packages/core/src/tests/utils/circularbuffer.test.ts @@ -0,0 +1,974 @@ +import { assert, test, expect, beforeEach } from 'vitest' +import { CircularBuffer } from '@/utils/circularbuffer' + +interface Context { + cbHT: CircularBuffer, // Head < Tail && !isFull + cbHTF: CircularBuffer, // Head < Tail && isFull + cbTH: CircularBuffer, // Tail < Head && !isFull + cbTHF: CircularBuffer // Tail < Head && isFull +} + +beforeEach(async (context: Context) => { + // Setup buffer state: Head < Tail && !isFull + // Expected: head=0, tail=2, size=2, [1, 2, undefined] + context.cbHT = new CircularBuffer(3) + context.cbHT.push_back(1) + context.cbHT.push_back(2) + + // Setup buffer state: Head < Tail && isFull + // Expected: head=0, tail=0, size=3, [1, 2, 3] + context.cbHTF = new CircularBuffer(3) + context.cbHTF.push_back(1) + context.cbHTF.push_back(2) + context.cbHTF.push_back(3) + + // Setup buffer state: Tail < Head && !isFull + // Expected: head=1, tail=0, size=2, [3, 1, 2] + context.cbTH = new CircularBuffer(3) + context.cbTH.push_back(0) + context.cbTH.push_back(1) + context.cbTH.push_back(2) + context.cbTH.push_back(3) + context.cbTH.pop_back() + + // Setup buffer state: Tail < Head && isFull + // Expected: head=1, tail=1, size=3, [3, 1, 2] + context.cbTHF = new CircularBuffer(3) + context.cbTHF.push_back(0) + context.cbTHF.push_back(1) + context.cbTHF.push_back(2) + context.cbTHF.push_back(3) +}) + +test('push_back', async () => { + const cb = new CircularBuffer(3); + + // Test initial empty state + // Expected: empty buffer with capacity 3 + assert.deepEqual(cb.getRawBuffer(), [undefined, undefined, undefined]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 0) + assert.equal(cb.isFull(), false) + assert.equal(cb.capacity(), 3) + assert.equal(cb.size(), 0) + + // Test first element insertion + // Expected: element at index 0, tail moves to 1 + cb.push_back(1); + assert.deepEqual(cb.getRawBuffer(), [1, undefined, undefined]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 1) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 1) + assert.equal(cb.get(0), 1) + + // Test second element insertion + // Expected: element at index 1, tail moves to 2 + cb.push_back(2); + assert.deepEqual(cb.getRawBuffer(), [1, 2, undefined]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 2) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 2) + assert.equal(cb.get(1), 2) + + // Test buffer becomes full + // Expected: element at index 2, tail wraps to 0, buffer is full + cb.push_back(3); + assert.deepEqual(cb.getRawBuffer(), [1, 2, 3]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 0) + assert.equal(cb.isFull(), true) + assert.equal(cb.size(), 3) + assert.equal(cb.get(2), 3) + + // Test overflow behavior + // Expected: oldest element overwritten, head moves to maintain size + cb.push_back(4); + assert.deepEqual(cb.getRawBuffer(), [4, 2, 3]) + assert.equal(cb.getHead(), 1) + assert.equal(cb.getTail(), 1) + assert.equal(cb.isFull(), true) + assert.equal(cb.size(), 3) + assert.equal(cb.get(2), 4) +}) + +test('pop_back by 1', async () => { + const cb = new CircularBuffer(3); + cb.push_back(1) + + // Test buffer state before pop + // Expected: one element, tail at position 1 + assert.deepEqual(cb.getRawBuffer(), [1, undefined, undefined]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 1) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 1) + + // Test removing last element + // Expected: buffer becomes empty, tail moves back to head + cb.pop_back(); + assert.deepEqual(cb.getRawBuffer(), [1, undefined, undefined]) // Data remains but logically removed + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 0) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 0) +}) + +test('pop_back multi', async () => { + const cb = new CircularBuffer(3); + cb.push_back(1) + cb.push_back(2) + + // Test buffer state with 2 elements + // Expected: two elements, tail at position 2 + assert.deepEqual(cb.getRawBuffer(), [1, 2, undefined]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 2) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 2) + + // Test removing multiple elements at once + // Expected: buffer becomes empty + cb.pop_back(2); + assert.deepEqual(cb.getRawBuffer(), [1, 2, undefined]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 0) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 0) + + // Test removing more elements than available + // Expected: all elements removed, no errors + cb.push_back(1) + cb.push_back(2) + assert.deepEqual(cb.getRawBuffer(), [1, 2, undefined]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 2) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 2) + + cb.pop_back(200); + assert.deepEqual(cb.getRawBuffer(), [1, 2, undefined]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 0) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 0) +}) + +test('alloc_back by 1', async () => { + const cb = new CircularBuffer(3); + + // Test allocating space without setting value + // Expected: size increases but no value is set + cb.alloc_back() + assert.deepEqual(cb.getRawBuffer(), [undefined, undefined, undefined]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 1) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 1) + + // Test second allocation + // Expected: tail moves to position 2 + cb.alloc_back(); + assert.deepEqual(cb.getRawBuffer(), [undefined, undefined, undefined]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 2) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 2) + + // Test allocation makes buffer full + // Expected: buffer becomes full, tail wraps to 0 + cb.alloc_back(); + assert.deepEqual(cb.getRawBuffer(), [undefined, undefined, undefined]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 0) + assert.equal(cb.isFull(), true) + assert.equal(cb.size(), 3) + + // Test allocation on full buffer + // Expected: head moves to maintain capacity + cb.alloc_back(); + assert.deepEqual(cb.getRawBuffer(), [undefined, undefined, undefined]) + assert.equal(cb.getHead(), 1) + assert.equal(cb.getTail(), 1) + assert.equal(cb.isFull(), true) + assert.equal(cb.size(), 3) +}) + +test('alloc_back multi', async () => { + const cb = new CircularBuffer(3); + + // Test allocating more space than capacity + // Expected: buffer becomes full with head/tail adjusted + cb.alloc_back(4) + assert.deepEqual(cb.getRawBuffer(), [undefined, undefined, undefined]) + assert.equal(cb.getHead(), 1) + assert.equal(cb.getTail(), 1) + assert.equal(cb.isFull(), true) + assert.equal(cb.size(), 3) +}) + +test('push_front', async () => { + const cb = new CircularBuffer(3); + + // Test initial state + // Expected: empty buffer + assert.deepEqual(cb.getRawBuffer(), [undefined, undefined, undefined]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 0) + assert.equal(cb.isFull(), false) + assert.equal(cb.capacity(), 3) + assert.equal(cb.size(), 0) + + // Test first front insertion + // Expected: element at last position, head moves to 2 + cb.push_front(1); + assert.deepEqual(cb.getRawBuffer(), [undefined, undefined, 1]) + assert.equal(cb.getHead(), 2) + assert.equal(cb.getTail(), 0) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 1) + + // Test second front insertion + // Expected: element at position 1, head moves to 1 + cb.push_front(2); + assert.deepEqual(cb.getRawBuffer(), [undefined, 2, 1]) + assert.equal(cb.getHead(), 1) + assert.equal(cb.getTail(), 0) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 2) + + // Test buffer becomes full + // Expected: element at position 0, buffer is full + cb.push_front(3); + assert.deepEqual(cb.getRawBuffer(), [3, 2, 1]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 0) + assert.equal(cb.isFull(), true) + assert.equal(cb.size(), 3) + + // Test overflow from front + // Expected: tail element overwritten, tail moves + cb.push_front(4); + assert.deepEqual(cb.getRawBuffer(), [3, 2, 4]) + assert.equal(cb.getHead(), 2) + assert.equal(cb.getTail(), 2) + assert.equal(cb.isFull(), true) + assert.equal(cb.size(), 3) +}) + +test('push_front, Head > 0', async () => { + const cb = new CircularBuffer(3); + + // Setup buffer with head at position 1 + // Expected: empty buffer with head/tail at position 1 + cb.push_front(undefined); + cb.push_front(undefined); + cb.pop_back(2); + assert.deepEqual(cb.getRawBuffer(), [undefined, undefined, undefined]) + assert.equal(cb.getHead(), 1) + assert.equal(cb.getTail(), 1) + assert.equal(cb.isFull(), false) + assert.equal(cb.capacity(), 3) + assert.equal(cb.size(), 0) + + // Test front insertion when head > 0 + // Expected: element at position 0, head wraps to 0 + cb.push_front(1); + assert.deepEqual(cb.getRawBuffer(), [1, undefined, undefined]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 1) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 1) + + // Test second front insertion + // Expected: element at position 2, head moves to 2 + cb.push_front(2); + assert.deepEqual(cb.getRawBuffer(), [1, undefined, 2]) + assert.equal(cb.getHead(), 2) + assert.equal(cb.getTail(), 1) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 2) + + // Test buffer becomes full + // Expected: buffer is full with wrapped positioning + cb.push_front(3); + assert.deepEqual(cb.getRawBuffer(), [1, 3, 2]) + assert.equal(cb.getHead(), 1) + assert.equal(cb.getTail(), 1) + assert.equal(cb.isFull(), true) + assert.equal(cb.size(), 3) + + // Test overflow behavior + // Expected: tail element overwritten + cb.push_front(4); + assert.deepEqual(cb.getRawBuffer(), [4, 3, 2]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 0) + assert.equal(cb.isFull(), true) + assert.equal(cb.size(), 3) +}) + +test('pop_front by 1', async () => { + const cb = new CircularBuffer(3); + cb.push_back(1) + + // Test buffer state before pop_front + // Expected: one element from back + assert.deepEqual(cb.getRawBuffer(), [1, undefined, undefined]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 1) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 1) + + // Test removing from front + // Expected: buffer becomes empty, head moves forward + cb.pop_front(); + assert.deepEqual(cb.getRawBuffer(), [1, undefined, undefined]) + assert.equal(cb.getHead(), 1) + assert.equal(cb.getTail(), 1) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 0) +}) + +test('pop_front multi', async () => { + const cb = new CircularBuffer(3); + cb.push_back(1) + cb.push_back(2) + + // Test buffer with 2 elements + // Expected: elements [1, 2] + assert.deepEqual(cb.getRawBuffer(), [1, 2, undefined]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 2) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 2) + + // Test removing multiple elements from front + // Expected: buffer becomes empty, head moves to tail position + cb.pop_front(2); + assert.deepEqual(cb.getRawBuffer(), [1, 2, undefined]) + assert.equal(cb.getHead(), 2) + assert.equal(cb.getTail(), 2) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 0) + + // Test adding after pop_front with wrapped indices + // Expected: elements added at wrapped positions + cb.push_back(1) + cb.push_back(2) + assert.deepEqual(cb.getRawBuffer(), [2, 2, 1]) + assert.equal(cb.getHead(), 2) + assert.equal(cb.getTail(), 1) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 2) + + // Test removing more than available + // Expected: all elements removed gracefully + cb.pop_front(200); + assert.deepEqual(cb.getRawBuffer(), [2, 2, 1]) + assert.equal(cb.getHead(), 1) + assert.equal(cb.getTail(), 1) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 0) +}) + +test('alloc_front by 1', async () => { + const cb = new CircularBuffer(3); + + // Test allocating space at front + // Expected: size increases, head moves backward + cb.alloc_front() + assert.deepEqual(cb.getRawBuffer(), [undefined, undefined, undefined]) + assert.equal(cb.getHead(), 2) + assert.equal(cb.getTail(), 0) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 1) + + // Test second front allocation + // Expected: head moves to position 1 + cb.alloc_front(); + assert.deepEqual(cb.getRawBuffer(), [undefined, undefined, undefined]) + assert.equal(cb.getHead(), 1) + assert.equal(cb.getTail(), 0) + assert.equal(cb.isFull(), false) + assert.equal(cb.size(), 2) + + // Test allocation makes buffer full + // Expected: buffer becomes full + cb.alloc_front(); + assert.deepEqual(cb.getRawBuffer(), [undefined, undefined, undefined]) + assert.equal(cb.getHead(), 0) + assert.equal(cb.getTail(), 0) + assert.equal(cb.isFull(), true) + assert.equal(cb.size(), 3) + + // Test allocation on full buffer + // Expected: tail moves to maintain capacity + cb.alloc_front(); + assert.deepEqual(cb.getRawBuffer(), [undefined, undefined, undefined]) + assert.equal(cb.getHead(), 2) + assert.equal(cb.getTail(), 2) + assert.equal(cb.isFull(), true) + assert.equal(cb.size(), 3) +}) + +test('alloc_front multi', async () => { + const cb = new CircularBuffer(3); + + // Test allocating more space than capacity from front + // Expected: buffer becomes full with proper head/tail positioning + cb.alloc_front(4) + assert.deepEqual(cb.getRawBuffer(), [undefined, undefined, undefined]) + assert.equal(cb.getHead(), 2) + assert.equal(cb.getTail(), 2) + assert.equal(cb.isFull(), true) + assert.equal(cb.size(), 3) +}) + +test('test context', async (context: Context) => { + const { cbHT, cbHTF, cbTH, cbTHF } = context + + // Verify Head < Tail && !Full state + // Expected: 2 elements with head=0, tail=2 + assert.deepEqual(cbHT.getRawBuffer(), [1, 2, undefined]) + assert.equal(cbHT.getHead(), 0) + assert.equal(cbHT.getTail(), 2) + assert.equal(cbHT.isFull(), false) + assert.equal(cbHT.size(), 2) + + // Verify Head < Tail && Full state + // Expected: 3 elements with head=0, tail=0 (wrapped) + assert.deepEqual(cbHTF.getRawBuffer(), [1, 2, 3]) + assert.equal(cbHTF.getHead(), 0) + assert.equal(cbHTF.getTail(), 0) + assert.equal(cbHTF.isFull(), true) + assert.equal(cbHTF.size(), 3) + + // Verify Tail < Head && !Full state + // Expected: 2 elements with head=1, tail=0 + assert.deepEqual(cbTH.getRawBuffer(), [3, 1, 2]) + assert.equal(cbTH.getHead(), 1) + assert.equal(cbTH.getTail(), 0) + assert.equal(cbTH.isFull(), false) + assert.equal(cbTH.size(), 2) + + // Verify Tail < Head && Full state + // Expected: 3 elements with head=1, tail=1 + assert.deepEqual(cbTHF.getRawBuffer(), [3, 1, 2]) + assert.equal(cbTHF.getHead(), 1) + assert.equal(cbTHF.getTail(), 1) + assert.equal(cbTHF.isFull(), true) + assert.equal(cbTHF.size(), 3) +}) + +test('back and pop_back, Head < Tail && !Full', async (context: Context) => { + const cb = context.cbHT + + // Test accessing last element + // Expected: returns last inserted element + assert.equal(cb.back(), 2) + assert.equal(cb.size(), 2) + + // Test removing last element + // Expected: previous element becomes last + cb.pop_back(); + assert.equal(cb.back(), 1) + assert.equal(cb.size(), 1) + + // Test removing last remaining element + // Expected: buffer becomes empty + cb.pop_back(); + assert.equal(cb.size(), 0) +}) + +test('back and pop_back, Head < Tail && Full', async (context: Context) => { + const cb = context.cbHTF + + // Test accessing last element in full buffer + // Expected: returns newest element + assert.equal(cb.back(), 3) + assert.equal(cb.size(), 3) + + // Test sequential removal from back + // Expected: elements removed in reverse order + cb.pop_back(); + assert.equal(cb.back(), 2) + assert.equal(cb.size(), 2) + cb.pop_back(); + assert.equal(cb.back(), 1) + assert.equal(cb.size(), 1) + cb.pop_back(); + assert.equal(cb.size(), 0) +}) + +test('back and pop_back, Head > Tail && !Full', async (context: Context) => { + const cb = context.cbTH + + // Test back element in wrapped state + // Expected: returns logically last element + assert.equal(cb.back(), 2) + assert.equal(cb.size(), 2) + cb.pop_back(); + assert.equal(cb.back(), 1) + assert.equal(cb.size(), 1) + cb.pop_back(); + assert.equal(cb.size(), 0) +}) + +test('back and pop_back, Head > Tail && Full', async (context: Context) => { + const cb = context.cbTHF + + // Test back element in full wrapped state + // Expected: handles wrapped indexing correctly + assert.equal(cb.back(), 3) + assert.equal(cb.size(), 3) + cb.pop_back(); + assert.equal(cb.back(), 2) + assert.equal(cb.size(), 2) + cb.pop_back(); + assert.equal(cb.back(), 1) + assert.equal(cb.size(), 1) + cb.pop_back(); + assert.equal(cb.size(), 0) +}) + +test('front and pop_front, Head < Tail && !Full', async (context: Context) => { + const cb = context.cbHT + + // Test accessing first element + // Expected: returns oldest element + assert.equal(cb.front(), 1) + assert.equal(cb.size(), 2) + + // Test removing from front + // Expected: next element becomes first + cb.pop_front(); + assert.equal(cb.front(), 2) + assert.equal(cb.size(), 1) + cb.pop_front(); + assert.equal(cb.size(), 0) +}) + +test('front and pop_front, Head < Tail && Full', async (context: Context) => { + const cb = context.cbHTF + + // Test front element access in full buffer + // Expected: returns oldest element + assert.equal(cb.front(), 1) + assert.equal(cb.size(), 3) + + // Test sequential removal from front + // Expected: elements removed in insertion order + cb.pop_front(); + assert.equal(cb.front(), 2) + assert.equal(cb.size(), 2) + cb.pop_front(); + assert.equal(cb.front(), 3) + assert.equal(cb.size(), 1) + cb.pop_front(); + assert.equal(cb.size(), 0) +}) + +test('front and pop_front, Head > Tail && !Full', async (context: Context) => { + const cb = context.cbTH + + // Test front element in wrapped state + // Expected: handles wrapped indexing for oldest element + assert.equal(cb.front(), 1) + assert.equal(cb.size(), 2) + cb.pop_front(); + assert.equal(cb.front(), 2) + assert.equal(cb.size(), 1) + cb.pop_front(); + assert.equal(cb.size(), 0) +}) + +test('front and pop_front, Head > Tail && Full', async (context: Context) => { + const cb = context.cbTHF + + // Test front element in full wrapped state + // Expected: correctly identifies oldest element in wrapped buffer + assert.equal(cb.front(), 1) + assert.equal(cb.size(), 3) + cb.pop_front(); + assert.equal(cb.front(), 2) + assert.equal(cb.size(), 2) + cb.pop_front(); + assert.equal(cb.front(), 3) + assert.equal(cb.size(), 1) + cb.pop_front(); + assert.equal(cb.size(), 0) +}) + +test('index access exceptions', async () => { + const cb = new CircularBuffer(3) + + // Test error handling for invalid index access on empty buffer + // Expected: proper error messages for out-of-range access + expect(() => cb.set(0, 1)).toThrowError('circularbuffer.set: index 0 converted to table index 0 out of range') + expect(() => cb[0] = 1).toThrowError('circularbuffer.set: index 0 converted to table index 0 out of range') + expect(() => cb.get(0)).toThrowError('circularbuffer.get: index 0 converted to table index 0 out of range') + expect(() => cb[0]).toThrowError('circularbuffer.get: index 0 converted to table index 0 out of range') +}) + +test('index access get', async (context: Context) => { + let cb + + // Test indexed access for Head < Tail && !Full + // Expected: logical indices map to correct values + cb = context.cbHT + assert.equal(cb.get(0), 1) // First element + assert.equal(cb.get(1), 2) // Second element + + // Test indexed access for Head < Tail && Full + // Expected: all three elements accessible by logical index + cb = context.cbHTF + assert.equal(cb.get(0), 1) + assert.equal(cb.get(1), 2) + assert.equal(cb.get(2), 3) + + // Test indexed access for Tail < Head && !Full + // Expected: wrapped buffer indices work correctly + cb = context.cbTH + assert.equal(cb.get(0), 1) + assert.equal(cb.get(1), 2) + + // Test indexed access for Tail < Head && Full + // Expected: full wrapped buffer accessible + cb = context.cbTHF + assert.equal(cb.get(0), 1) + assert.equal(cb.get(1), 2) + assert.equal(cb.get(2), 3) +}) + +test('index access set', async (context: Context) => { + let cb + + // Test setting values by index for Head < Tail && !Full + // Expected: values updated and retrievable + cb = context.cbHT + cb.set(0, 11) + cb.set(1, 22) + assert.equal(cb.get(0), 11) + assert.equal(cb.get(1), 22) + + // Test setting values for Head < Tail && Full + // Expected: all positions can be updated + cb = context.cbHTF + cb.set(0, 11) + cb.set(1, 22) + cb.set(2, 33) + assert.equal(cb.get(0), 11) + assert.equal(cb.get(1), 22) + assert.equal(cb.get(2), 33) + + // Test setting values for Tail < Head && !Full + // Expected: wrapped indices handle updates correctly + cb = context.cbTH + cb.set(0, 11) + cb.set(1, 22) + assert.equal(cb.get(0), 11) + assert.equal(cb.get(1), 22) + + // Test setting values for Tail < Head && Full + // Expected: full wrapped buffer can be updated + cb = context.cbTHF + cb.set(0, 11) + cb.set(1, 22) + cb.set(2, 33) + assert.equal(cb.get(0), 11) + assert.equal(cb.get(1), 22) + assert.equal(cb.get(2), 33) +}) + +test('index access operator []', async (context: Context) => { + let cb + + // Test bracket operator for different buffer states + // Expected: bracket notation works same as get() method + cb = context.cbTH; + assert.equal(cb[0], 1) + assert.equal(cb[1], 2) + + cb = context.cbTHF; + assert.equal(cb[0], 1) + assert.equal(cb[1], 2) + assert.equal(cb[2], 3) + + cb = context.cbHT; + assert.equal(cb[0], 1) + assert.equal(cb[1], 2) + + cb = context.cbHTF; + assert.equal(cb[0], 1) + assert.equal(cb[1], 2) + assert.equal(cb[2], 3) +}) + +class ComplexObject { + buf: Float32Array; + constructor() { + this.buf = new Float32Array(4) + } +} + +test('index access operator [] for complex objects', async () => { + const cb = new CircularBuffer(3) + const buf = cb.getRawBuffer() + + // Test storing complex objects in buffer + // Expected: objects can be stored and retrieved correctly + for (let i = 0; i < buf.length; ++i) { + buf[i] = new ComplexObject() + } + cb.alloc_back() + + assert.deepEqual(cb[0], new ComplexObject()) +}) + +test('clear method', async () => { + const cb = new CircularBuffer(3); + cb.push_back(1); + cb.push_back(2); + cb.push_back(3); + + // Test buffer state before clear + // Expected: buffer is full + assert.equal(cb.size(), 3); + assert.equal(cb.isFull(), true); + + // Test clear operation + // Expected: buffer returns to initial empty state + cb.clear(); + assert.equal(cb.size(), 0); + assert.equal(cb.isEmpty(), true); + assert.equal(cb.isFull(), false); + assert.equal(cb.getHead(), 0); + assert.equal(cb.getTail(), 0); +}); + +test('isEmpty method', async () => { + const cb = new CircularBuffer(3); + + // Test initial empty state + // Expected: buffer reports as empty + assert.equal(cb.isEmpty(), true); + + // Test after adding element + // Expected: buffer is no longer empty + cb.push_back(1); + assert.equal(cb.isEmpty(), false); + + // Test after removing element + // Expected: buffer returns to empty state + cb.pop_back(); + assert.equal(cb.isEmpty(), true); +}); + +test('edge cases with count = 0', async () => { + const cb = new CircularBuffer(3); + cb.push_back(1); + cb.push_back(2); + + const sizeBefore = cb.size(); + + // Test operations with count = 0 + // Expected: no changes to buffer state + cb.pop_front(0); + assert.equal(cb.size(), sizeBefore); + + cb.pop_back(0); + assert.equal(cb.size(), sizeBefore); + + cb.alloc_front(0); + assert.equal(cb.size(), sizeBefore); + + cb.alloc_back(0); + assert.equal(cb.size(), sizeBefore); +}); + +test('negative count parameters', async () => { + const cb = new CircularBuffer(3); + cb.push_back(1); + cb.push_back(2); + + const sizeBefore = cb.size(); + + // Test operations with negative counts + // Expected: operations ignored, no state changes + cb.pop_front(-1); + assert.equal(cb.size(), sizeBefore); + + cb.pop_back(-5); + assert.equal(cb.size(), sizeBefore); + + cb.alloc_front(-1); + assert.equal(cb.size(), sizeBefore); + + cb.alloc_back(-3); + assert.equal(cb.size(), sizeBefore); +}); + +test('front and back on empty buffer', async () => { + const cb = new CircularBuffer(3); + + // Test accessing front/back of empty buffer + // Expected: returns undefined for empty buffer + assert.equal(cb.front(), undefined); + assert.equal(cb.back(), undefined); +}); + +test('proxy set with non-numeric keys', async () => { + const cb = new CircularBuffer(3); + cb.push_back(1); + + // Test setting custom properties + // Expected: custom properties can be set without interfering with buffer + (cb as any).customProperty = 'test'; + assert.equal((cb as any).customProperty, 'test'); + + // Test setting custom methods + // Expected: original functionality preserved + const originalSize = cb.size; + (cb as any).testMethod = () => 'test'; + assert.equal((cb as any).testMethod(), 'test'); + assert.equal(cb.size(), 1); +}); + +test('proxy get with non-numeric keys', async () => { + const cb = new CircularBuffer(3); + + // Test accessing existing methods through proxy + // Expected: methods are accessible and functional + assert.equal(typeof cb.size, 'function'); + assert.equal(typeof cb.push_back, 'function'); + assert.equal(typeof cb.capacity, 'function'); + + // Test accessing non-existent properties + // Expected: returns undefined for non-existent properties + assert.equal((cb as any).nonExistent, undefined); +}); + +test('complex buffer state transitions', async () => { + const cb = new CircularBuffer(4); + + // Test filling buffer completely + // Expected: buffer reaches full capacity + cb.push_back(1); + cb.push_back(2); + cb.push_back(3); + cb.push_back(4); + assert.equal(cb.isFull(), true); + + // Test partial removal and addition with wrapping + // Expected: circular behavior maintains correct element order + cb.pop_front(2); + assert.equal(cb.size(), 2); + assert.equal(cb.front(), 3); + assert.equal(cb.back(), 4); + + cb.push_back(5); + cb.push_back(6); + assert.equal(cb.isFull(), true); + assert.deepEqual([cb[0], cb[1], cb[2], cb[3]], [3, 4, 5, 6]); +}); + +test('large buffer operations', async () => { + const cb = new CircularBuffer(1000); + + // Test performance and correctness with large buffer + // Expected: handles large number of elements efficiently + for (let i = 0; i < 1000; i++) { + cb.push_back(i); + } + assert.equal(cb.size(), 1000); + assert.equal(cb.isFull(), true); + + // Test bulk removal operations + // Expected: maintains correct state after large operations + cb.pop_front(500); + assert.equal(cb.size(), 500); + assert.equal(cb.front(), 500); + assert.equal(cb.back(), 999); +}); + +test('waitForChangeSize functionality', async () => { + const cb = new CircularBuffer(3); + + // Test async notification of size changes + // Expected: promise resolves when size changes + const promise = cb.waitForChangeSize(); + + // Trigger size change asynchronously + setTimeout(() => cb.push_back(1), 10); + + await promise; + assert.equal(cb.size(), 1); +}); + +test('onChangeSize callback', async () => { + const cb = new CircularBuffer(3); + let callCount = 0; + + // Test callback mechanism for size changes + // Expected: callback invoked for each size-changing operation + cb.onChangeSize = () => callCount++; + + cb.push_back(1); + assert.equal(callCount, 1); + + cb.push_front(2); + assert.equal(callCount, 2); + + cb.pop_back(); + assert.equal(callCount, 3); + + cb.clear(); + assert.equal(callCount, 4); +}); + +test('buffer with capacity 1', async () => { + const cb = new CircularBuffer(1); + + // Test edge case of minimum capacity + // Expected: single-element buffer works correctly + assert.equal(cb.capacity(), 1); + assert.equal(cb.size(), 0); + + cb.push_back(1); + assert.equal(cb.size(), 1); + assert.equal(cb.isFull(), true); + assert.equal(cb[0], 1); + + // Test overflow behavior with capacity 1 + // Expected: new element replaces the only element + cb.push_back(2); + assert.equal(cb.size(), 1); + assert.equal(cb[0], 2); +}); + +test('alloc operations edge cases', async () => { + const cb = new CircularBuffer(3); + + // Test alloc_back on full buffer + // Expected: removes elements from front to make space + cb.push_back(1); + cb.push_back(2); + cb.push_back(3); + + cb.alloc_back(2); + assert.equal(cb.size(), 3); + assert.equal(cb.front(), 3); // First elements should be removed + + // Test alloc_front on full buffer + // Expected: removes elements from back to make space + cb.clear(); + cb.push_back(1); + cb.push_back(2); + cb.push_back(3); + + cb.alloc_front(2); + assert.equal(cb.size(), 3); + assert.equal(cb.back(), 1); // Last elements should be removed +}); \ No newline at end of file diff --git a/packages/core/src/tests/utils/convUtils.test.ts b/packages/core/src/tests/utils/convUtils.test.ts new file mode 100644 index 0000000..2e31835 --- /dev/null +++ b/packages/core/src/tests/utils/convUtils.test.ts @@ -0,0 +1,45 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { stringToBoolean } from "@/utils/convUtils"; + +describe("stringToBoolean", () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it("returns true for truthy textual values (case-insensitive, trimmed)", () => { + expect(stringToBoolean("true")).toBe(true); + expect(stringToBoolean(" TrUe ")).toBe(true); + expect(stringToBoolean("yes")).toBe(true); + expect(stringToBoolean(" YES")).toBe(true); + expect(stringToBoolean("1")).toBe(true); + }); + + it("returns false for explicit falsey textual values", () => { + expect(stringToBoolean("false")).toBe(false); + expect(stringToBoolean("No")).toBe(false); + expect(stringToBoolean("0")).toBe(false); + expect(stringToBoolean("")).toBe(false); + }); + + it("parses JSON for other strings (e.g. \"null\" -> null)", () => { + expect(stringToBoolean("null")).toBe(false); + }); + + it("returns false and logs an error for invalid JSON strings", () => { + const spy = vi.spyOn(console, "error").mockImplementation(() => { }); + expect(stringToBoolean("not a json")).toBe(false); + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it("handles actual null and undefined inputs by returning false and logging an error", () => { + const spy = vi.spyOn(console, "error").mockImplementation(() => { }); + + // call with values that violate the declared type to exercise runtime behavior + expect(stringToBoolean('asd')).toBe(false); + + // console.error should have been called at least once for the thrown JSON.parse or type error + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); +}); \ No newline at end of file diff --git a/packages/core/src/tests/utils/journal.test.ts b/packages/core/src/tests/utils/journal.test.ts new file mode 100644 index 0000000..61ad7ef --- /dev/null +++ b/packages/core/src/tests/utils/journal.test.ts @@ -0,0 +1,69 @@ +import { describe, it, expect } from 'vitest'; +import { JournalLogLevel } from '@/utils/journal'; +import type { JournalLogItem, JournalLogLevelKeys } from '@/utils/journal'; + +describe('Logging Utils', () => { + describe('LogLevel enum', () => { + it('should have correct string values', () => { + expect(JournalLogLevel.DEBUG).toBe('DEBUG'); + expect(JournalLogLevel.INFO).toBe('INFO'); + expect(JournalLogLevel.WARNING).toBe('WARNING'); + expect(JournalLogLevel.ERROR).toBe('ERROR'); + expect(JournalLogLevel.FATAL).toBe('FATAL'); + }); + + it('should have all expected log levels', () => { + const levels = Object.values(JournalLogLevel); + expect(levels).toHaveLength(5); + expect(levels).toContain('DEBUG'); + expect(levels).toContain('INFO'); + expect(levels).toContain('WARNING'); + expect(levels).toContain('ERROR'); + expect(levels).toContain('FATAL'); + }); + }); + + describe('JournalLogItem interface', () => { + it('should accept valid log item structure', () => { + const logItem: JournalLogItem = { + timestamp: Date.now(), + subSystem: 'test-system', + logLevel: JournalLogLevel.INFO, + message: 'Test message' + }; + + expect(logItem.timestamp).toBeTypeOf('number'); + expect(logItem.subSystem).toBeTypeOf('string'); + expect(logItem.logLevel).toBe(JournalLogLevel.INFO); + expect(logItem.message).toBeTypeOf('string'); + }); + }); + + describe('Type compatibility', () => { + it('should work with JournalLogLevelKeys type', () => { + const keys: JournalLogLevelKeys[] = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'FATAL']; + + keys.forEach(key => { + expect(JournalLogLevel[key]).toBeDefined(); + expect(typeof JournalLogLevel[key]).toBe('string'); + }); + }); + + it('should create valid LogItem with all log levels', () => { + const timestamp = Date.now(); + const subSystem = 'test'; + const message = 'test message'; + + Object.values(JournalLogLevel).forEach(level => { + const logItem: JournalLogItem = { + timestamp, + subSystem, + logLevel: level, + message + }; + + expect(logItem.logLevel).toBe(level); + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/core/src/tests/utils/logger.test.ts b/packages/core/src/tests/utils/logger.test.ts new file mode 100644 index 0000000..ddbd85c --- /dev/null +++ b/packages/core/src/tests/utils/logger.test.ts @@ -0,0 +1,327 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { SimpleLogger, LogLevels } from '@/utils/logger'; +import type { LoggerInterface, LogLevel } from '@/utils/logger'; + +describe('Logger', () => { + let logger: SimpleLogger; + let loggerWithContext: SimpleLogger; + let consoleLogSpy: any; + let consoleErrorSpy: any; + let consoleDebugSpy: any; + let consoleInfoSpy: any; + let consoleWarnSpy: any; + + beforeEach(() => { + logger = new SimpleLogger(); + loggerWithContext = new SimpleLogger('TestContext'); + consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { }); + consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { }); + consoleDebugSpy = vi.spyOn(console, 'debug').mockImplementation(() => { }); + consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation(() => { }); + consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('LOG_LEVELS', () => { + it('should export correct log levels', () => { + expect(LogLevels).toEqual(["verbose", "debug", "log", "warn", "error", "fatal"]); + }); + }); + + describe('LoggerInterface', () => { + it('should define required methods', () => { + expect(typeof logger.log).toBe('function'); + expect(typeof logger.error).toBe('function'); + expect(typeof logger.warn).toBe('function'); + }); + + it('should define optional methods', () => { + expect(typeof logger.debug).toBe('function'); + expect(typeof logger.verbose).toBe('function'); + expect(typeof logger.fatal).toBe('function'); + }); + }); + + describe('SimpleLogger Constructor', () => { + it('should create logger without context', () => { + const loggerNoContext = new SimpleLogger(); + expect(loggerNoContext).toBeInstanceOf(SimpleLogger); + }); + + it('should create logger with context', () => { + const loggerWithCtx = new SimpleLogger('MyContext'); + expect(loggerWithCtx).toBeInstanceOf(SimpleLogger); + }); + + it('should create logger with undefined context', () => { + const loggerUndefinedCtx = new SimpleLogger(undefined); + expect(loggerUndefinedCtx).toBeInstanceOf(SimpleLogger); + }); + }); + + describe('SimpleLogger without context', () => { + describe('debug method', () => { + it('should call console.debug with message only', () => { + const message = 'Debug message'; + logger.debug(message); + + expect(consoleDebugSpy).toHaveBeenCalledTimes(1); + expect(consoleDebugSpy).toHaveBeenCalledWith(message); + }); + + it('should call console.debug with message and optional parameters', () => { + const message = 'Debug message'; + const param1 = { key: 'value' }; + const param2 = 123; + const param3 = ['array', 'item']; + + logger.debug(message, param1, param2, param3); + + expect(consoleDebugSpy).toHaveBeenCalledTimes(1); + expect(consoleDebugSpy).toHaveBeenCalledWith(message, param1, param2, param3); + }); + + it('should handle empty message', () => { + logger.debug(''); + + expect(consoleDebugSpy).toHaveBeenCalledTimes(1); + expect(consoleDebugSpy).toHaveBeenCalledWith(''); + }); + + it('should handle undefined and null parameters', () => { + const message = 'Debug with null/undefined'; + logger.debug(message, undefined, null); + + expect(consoleDebugSpy).toHaveBeenCalledTimes(1); + expect(consoleDebugSpy).toHaveBeenCalledWith(message, undefined, null); + }); + + it('should handle complex objects as parameters', () => { + const message = 'Debug with complex data'; + const complexObj = { + nested: { deep: { value: 'test' } }, + array: [1, 2, 3], + func: () => 'function' + }; + + logger.debug(message, complexObj); + + expect(consoleDebugSpy).toHaveBeenCalledTimes(1); + expect(consoleDebugSpy).toHaveBeenCalledWith(message, complexObj); + }); + }); + + describe('other methods without context', () => { + it('should call console.log with message and parameters', () => { + const message = 'Log message'; + const param = 'extra'; + + logger.log(message, param); + + expect(consoleLogSpy).toHaveBeenCalledTimes(1); + expect(consoleLogSpy).toHaveBeenCalledWith(message, param); + }); + + it('should call console.error with message and parameters', () => { + const message = 'Error message'; + const param = 'error details'; + + logger.error(message, param); + + expect(consoleErrorSpy).toHaveBeenCalledTimes(1); + expect(consoleErrorSpy).toHaveBeenCalledWith(message, param); + }); + + it('should call console.info for verbose with message and parameters', () => { + const message = 'Verbose message'; + const param = 'verbose details'; + + logger.verbose(message, param); + + expect(consoleInfoSpy).toHaveBeenCalledTimes(1); + expect(consoleInfoSpy).toHaveBeenCalledWith(message, param); + }); + + it('should call console.warn with message and parameters', () => { + const message = 'Warning message'; + const param = 'warning details'; + + logger.warn(message, param); + + expect(consoleWarnSpy).toHaveBeenCalledTimes(1); + expect(consoleWarnSpy).toHaveBeenCalledWith(message, param); + }); + + it('should call console.error for fatal messages', () => { + const message = 'Fatal error'; + const param = 'fatal details'; + + logger.fatal(message, param); + + expect(consoleErrorSpy).toHaveBeenCalledTimes(1); + expect(consoleErrorSpy).toHaveBeenCalledWith(message, param); + }); + }); + }); + + describe('SimpleLogger with context', () => { + describe('debug method with context', () => { + it('should call console.debug with formatted message', () => { + const message = 'Debug message'; + loggerWithContext.debug(message); + + expect(consoleDebugSpy).toHaveBeenCalledTimes(1); + expect(consoleDebugSpy).toHaveBeenCalledWith('[TestContext] Debug message'); + }); + + it('should call console.debug with formatted message and parameters', () => { + const message = 'Debug with params'; + const param1 = { data: 'test' }; + const param2 = 456; + + loggerWithContext.debug(message, param1, param2); + + expect(consoleDebugSpy).toHaveBeenCalledTimes(1); + expect(consoleDebugSpy).toHaveBeenCalledWith('[TestContext] Debug with params', param1, param2); + }); + + it('should handle empty message with context', () => { + loggerWithContext.debug(''); + + expect(consoleDebugSpy).toHaveBeenCalledTimes(1); + expect(consoleDebugSpy).toHaveBeenCalledWith('[TestContext] '); + }); + }); + + describe('other methods with context', () => { + it('should format log messages with context', () => { + const message = 'Log message'; + loggerWithContext.log(message); + + expect(consoleLogSpy).toHaveBeenCalledWith('[TestContext] Log message'); + }); + + it('should format error messages with context', () => { + const message = 'Error message'; + loggerWithContext.error(message); + + expect(consoleErrorSpy).toHaveBeenCalledWith('[TestContext] Error message'); + }); + + it('should format verbose messages with context', () => { + const message = 'Verbose message'; + loggerWithContext.verbose(message); + + expect(consoleInfoSpy).toHaveBeenCalledWith('[TestContext] Verbose message'); + }); + + it('should format warn messages with context', () => { + const message = 'Warning message'; + loggerWithContext.warn(message); + + expect(consoleWarnSpy).toHaveBeenCalledWith('[TestContext] Warning message'); + }); + + it('should format fatal messages with context', () => { + const message = 'Fatal error'; + loggerWithContext.fatal(message); + + expect(consoleErrorSpy).toHaveBeenCalledWith('[TestContext] Fatal error'); + }); + }); + + describe('context edge cases', () => { + it('should handle special characters in context', () => { + const specialLogger = new SimpleLogger('Test@Context#123'); + specialLogger.debug('Message'); + + expect(consoleDebugSpy).toHaveBeenCalledWith('[Test@Context#123] Message'); + }); + + it('should handle empty string context', () => { + const emptyContextLogger = new SimpleLogger(''); + emptyContextLogger.debug('Message'); + + expect(consoleDebugSpy).toHaveBeenCalledWith('[] Message'); + }); + + it('should handle whitespace context', () => { + const whitespaceLogger = new SimpleLogger(' '); + whitespaceLogger.debug('Message'); + + expect(consoleDebugSpy).toHaveBeenCalledWith('[ ] Message'); + }); + }); + }); + + describe('interface implementation', () => { + it('should implement LoggerInterface correctly', () => { + const loggerInterface: LoggerInterface = new SimpleLogger(); + + expect(loggerInterface).toBeInstanceOf(SimpleLogger); + expect(typeof loggerInterface.log).toBe('function'); + expect(typeof loggerInterface.error).toBe('function'); + expect(typeof loggerInterface.warn).toBe('function'); + expect(typeof loggerInterface.debug).toBe('function'); + expect(typeof loggerInterface.verbose).toBe('function'); + expect(typeof loggerInterface.fatal).toBe('function'); + }); + + it('should implement LoggerInterface with context correctly', () => { + const loggerInterface: LoggerInterface = new SimpleLogger('InterfaceContext'); + + expect(loggerInterface).toBeInstanceOf(SimpleLogger); + loggerInterface.debug?.('Test message'); + expect(consoleDebugSpy).toHaveBeenCalledWith('[InterfaceContext] Test message'); + }); + }); + + describe('multiple method calls', () => { + it('should handle multiple debug calls independently without context', () => { + logger.debug('First debug'); + logger.debug('Second debug', 'param'); + logger.debug('Third debug', 1, 2, 3); + + expect(consoleDebugSpy).toHaveBeenCalledTimes(3); + expect(consoleDebugSpy).toHaveBeenNthCalledWith(1, 'First debug'); + expect(consoleDebugSpy).toHaveBeenNthCalledWith(2, 'Second debug', 'param'); + expect(consoleDebugSpy).toHaveBeenNthCalledWith(3, 'Third debug', 1, 2, 3); + }); + + it('should handle multiple debug calls independently with context', () => { + loggerWithContext.debug('First debug'); + loggerWithContext.debug('Second debug', 'param'); + loggerWithContext.debug('Third debug', 1, 2, 3); + + expect(consoleDebugSpy).toHaveBeenCalledTimes(3); + expect(consoleDebugSpy).toHaveBeenNthCalledWith(1, '[TestContext] First debug'); + expect(consoleDebugSpy).toHaveBeenNthCalledWith(2, '[TestContext] Second debug', 'param'); + expect(consoleDebugSpy).toHaveBeenNthCalledWith(3, '[TestContext] Third debug', 1, 2, 3); + }); + + it('should handle mixed logger instances', () => { + logger.debug('No context'); + loggerWithContext.debug('With context'); + + expect(consoleDebugSpy).toHaveBeenCalledTimes(2); + expect(consoleDebugSpy).toHaveBeenNthCalledWith(1, 'No context'); + expect(consoleDebugSpy).toHaveBeenNthCalledWith(2, '[TestContext] With context'); + }); + }); + + describe('type compatibility', () => { + it('should work with LogLevel type', () => { + const level: LogLevel = 'debug'; + expect(LogLevels).toContain(level); + }); + + it('should handle all log levels', () => { + LogLevels.forEach(level => { + expect(typeof level).toBe('string'); + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/core/src/tests/utils/promiseHelper.test.ts b/packages/core/src/tests/utils/promiseHelper.test.ts new file mode 100644 index 0000000..c19a5ea --- /dev/null +++ b/packages/core/src/tests/utils/promiseHelper.test.ts @@ -0,0 +1,64 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { PromiseHelper } from '@/utils/promiseHelper'; + +describe('PromiseHelper', () => { + let ph: PromiseHelper; + + beforeEach(() => { + ph = new PromiseHelper(); + }); + + it('createPromise returns increasing ids and promise resolves via promiseResolve', async () => { + const [id1, p1] = ph.createPromise(); + const [id2, p2] = ph.createPromise(); + + expect(id2).toBeGreaterThan(id1); + + const ref1 = ph.getPromise(id1); + expect(ref1).toBeDefined(); + // resolve + ph.promiseResolve(ref1!, 'ok'); + await expect(p1).resolves.toBe('ok'); + + const ref2 = ph.getPromise(id2); + expect(ref2).toBeDefined(); + ph.promiseResolve(ref2!, 42); + await expect(p2).resolves.toBe(42); + }); + + it('promiseReject rejects promise via promiseReject', async () => { + const [id, p] = ph.createPromise(); + const ref = ph.getPromise(id); + expect(ref).toBeDefined(); + + ph.promiseReject(ref!, new Error('fail')); + await expect(p).rejects.toThrow('fail'); + }); + + it('deletePromise removes stored promise and returns boolean', () => { + const [id] = ph.createPromise(); + expect(ph.getPromise(id)).toBeDefined(); + const deleted = ph.deletePromise(id); + expect(deleted).toBe(true); + expect(ph.getPromise(id)).toBeUndefined(); + // deleting non-existent id returns false + expect(ph.deletePromise(9999)).toBe(false); + }); + + it('clear resets internal map and id counter', () => { + const [id] = ph.createPromise(); + expect(ph.getPromise(id)).toBeDefined(); + ph.clear(); + expect(ph.getPromise(id)).toBeUndefined(); + + const [newId] = ph.createPromise(); + // after clear id counter should restart from 1 + expect(newId).toBe(1); + }); + + it('promiseResolve / promiseReject are safe when given undefined', () => { + // should not throw + expect(() => ph.promiseResolve(undefined as any)).not.toThrow(); + expect(() => ph.promiseReject(undefined as any)).not.toThrow(); + }); +}); \ No newline at end of file diff --git a/packages/core/src/tests/utils/stringUtils.test.ts b/packages/core/src/tests/utils/stringUtils.test.ts new file mode 100644 index 0000000..16b2999 --- /dev/null +++ b/packages/core/src/tests/utils/stringUtils.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect } from 'vitest'; +import { containsAnySubstr } from '@/utils/stringUtils'; + +describe('containsAnySubstr', () => { + it('returns false when str is undefined or null', () => { + expect(containsAnySubstr(undefined, 'a')).toBe(false); + expect(containsAnySubstr(null, 'a')).toBe(false); + }); + + it('returns false when substrs is undefined or null', () => { + expect(containsAnySubstr('text', undefined)).toBe(false); + expect(containsAnySubstr('text', null)).toBe(false); + }); + + it('returns false for empty string as str', () => { + expect(containsAnySubstr('', 'a')).toBe(false); + }); + + it('handles single substring (non-array) and is case-insensitive by default', () => { + expect(containsAnySubstr('Hello World', 'hello')).toBe(true); + expect(containsAnySubstr('Hello World', 'WORLD')).toBe(true); + }); + + it('respects caseSensitive = true', () => { + expect(containsAnySubstr('Hello World', 'hello', true)).toBe(false); + expect(containsAnySubstr('Hello World', 'World', true)).toBe(true); + }); + + it('accepts array of substrings and returns true if any matches', () => { + expect(containsAnySubstr('abc def', ['x', 'DEF', 'y'])).toBe(true); + }); + + it('returns false when none of the substrings match', () => { + expect(containsAnySubstr('abcdef', ['x', 'y', 'z'])).toBe(false); + }); + + it('trims substrings and ignores empty/null/undefined substrings in the array', () => { + const arr = [undefined, null, ' ', ' def ', 'x']; + expect(containsAnySubstr('abcdef', arr)).toBe(true); // 'def' matches after trim + expect(containsAnySubstr('abcdef', [undefined, null, ' '])).toBe(false); // all ignored + }); + + it('does not trim the main string but still matches when substring trimmed', () => { + // main string has surrounding spaces but contains 'foo' sequence + expect(containsAnySubstr(' foo ', ' foo ')).toBe(true); + }); +}); \ No newline at end of file diff --git a/packages/core/src/tests/utils/time.test.ts b/packages/core/src/tests/utils/time.test.ts new file mode 100644 index 0000000..4bc598c --- /dev/null +++ b/packages/core/src/tests/utils/time.test.ts @@ -0,0 +1,194 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { sleep, usleep, now, timestampToTimeString } from '@/utils/time'; + +describe('Time Utils', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + describe('sleep', () => { + it('should resolve after specified seconds', async () => { + const promise = sleep(2); + + // Fast-forward time by 2 seconds + vi.advanceTimersByTime(2000); + + await expect(promise).resolves.toBeUndefined(); + }); + + it('should convert seconds to milliseconds correctly', async () => { + const spy = vi.spyOn(globalThis, 'setTimeout'); + + sleep(1.5); + + expect(spy).toHaveBeenCalledWith(expect.any(Function), 1500); + + spy.mockRestore(); + }); + + it('should handle zero seconds', async () => { + const promise = sleep(0); + + vi.advanceTimersByTime(0); + + await expect(promise).resolves.toBeUndefined(); + }); + + it('should handle fractional seconds', async () => { + const spy = vi.spyOn(globalThis, 'setTimeout'); + + sleep(0.5); + + expect(spy).toHaveBeenCalledWith(expect.any(Function), 500); + + spy.mockRestore(); + }); + }); + + describe('usleep', () => { + it('should resolve after specified milliseconds', async () => { + const promise = usleep(1000); + + vi.advanceTimersByTime(1000); + + await expect(promise).resolves.toBeUndefined(); + }); + + it('should use milliseconds directly', async () => { + const spy = vi.spyOn(globalThis, 'setTimeout'); + + usleep(250); + + expect(spy).toHaveBeenCalledWith(expect.any(Function), 250); + + spy.mockRestore(); + }); + + it('should handle zero milliseconds', async () => { + const promise = usleep(0); + + vi.advanceTimersByTime(0); + + await expect(promise).resolves.toBeUndefined(); + }); + }); + + describe('now', () => { + beforeEach(() => { + vi.useRealTimers(); // Use real timers for performance.now() + }); + + it('should return a number', () => { + const result = now(); + expect(typeof result).toBe('number'); + }); + + it('should return performance.now() value', () => { + const spy = vi.spyOn(performance, 'now').mockReturnValue(123.456); + + const result = now(); + + expect(result).toBe(123.456); + expect(spy).toHaveBeenCalled(); + + spy.mockRestore(); + }); + + it('should return increasing values on subsequent calls', () => { + const first = now(); + const second = now(); + + expect(second).toBeGreaterThanOrEqual(first); + }); + }); + + describe('timestampToTimeString', () => { + it('should format timestamp correctly with hours, minutes, seconds, and milliseconds', () => { + // January 1, 2024, 14:30:45.123 + const timestamp = new Date(2024, 0, 1, 14, 30, 45, 123).getTime(); + + const result = timestampToTimeString(timestamp); + + expect(result).toBe('14:30:45.123'); + }); + + it('should pad single digit hours, minutes, and seconds with zeros', () => { + // January 1, 2024, 09:05:03.050 + const timestamp = new Date(2024, 0, 1, 9, 5, 3, 50).getTime(); + + const result = timestampToTimeString(timestamp); + + expect(result).toBe('09:05:03.050'); + }); + + it('should pad single and double digit milliseconds with zeros', () => { + // Test single digit milliseconds + const timestamp1 = new Date(2024, 0, 1, 12, 0, 0, 7).getTime(); + expect(timestampToTimeString(timestamp1)).toBe('12:00:00.007'); + + // Test double digit milliseconds + const timestamp2 = new Date(2024, 0, 1, 12, 0, 0, 45).getTime(); + expect(timestampToTimeString(timestamp2)).toBe('12:00:00.045'); + + // Test triple digit milliseconds + const timestamp3 = new Date(2024, 0, 1, 12, 0, 0, 999).getTime(); + expect(timestampToTimeString(timestamp3)).toBe('12:00:00.999'); + }); + + it('should handle midnight correctly', () => { + const timestamp = new Date(2024, 0, 1, 0, 0, 0, 0).getTime(); + + const result = timestampToTimeString(timestamp); + + expect(result).toBe('00:00:00.000'); + }); + + it('should handle end of day correctly', () => { + const timestamp = new Date(2024, 0, 1, 23, 59, 59, 999).getTime(); + + const result = timestampToTimeString(timestamp); + + expect(result).toBe('23:59:59.999'); + }); + + it('should work with current timestamp', () => { + const timestamp = Date.now(); + const result = timestampToTimeString(timestamp); + + // Should match the pattern HH:MM:SS.mmm + expect(result).toMatch(/^\d{2}:\d{2}:\d{2}\.\d{3}$/); + }); + + it('should handle different timezones consistently', () => { + // Create timestamp in a specific timezone context + const timestamp = 1704110445123; // Fixed timestamp + const result = timestampToTimeString(timestamp); + + // Should always format the same way regardless of system timezone + expect(typeof result).toBe('string'); + expect(result).toMatch(/^\d{2}:\d{2}:\d{2}\.\d{3}$/); + }); + }); + + describe('integration tests', () => { + it('should work together - sleep and timestampToTimeString', async () => { + vi.useRealTimers(); + + const before = Date.now(); + const beforeString = timestampToTimeString(before); + + await usleep(10); // Small delay + + const after = Date.now(); + const afterString = timestampToTimeString(after); + + expect(beforeString).toMatch(/^\d{2}:\d{2}:\d{2}\.\d{3}$/); + expect(afterString).toMatch(/^\d{2}:\d{2}:\d{2}\.\d{3}$/); + expect(after).toBeGreaterThan(before); + }); + }); +}); \ No newline at end of file diff --git a/packages/core/src/transform/converters.ts b/packages/core/src/transform/converters.ts new file mode 100644 index 0000000..f878e8f --- /dev/null +++ b/packages/core/src/transform/converters.ts @@ -0,0 +1,51 @@ +/** + * Converts an Int16Array buffer to a Float32Array buffer. + * @param input The input Int16Array buffer. + * @param outbuf The output Float32Array buffer (optional) + * if not provided, a new buffer will be created. + * @returns The converted Float32Array buffer. + */ +export function bufferI16ToF32(input: Int16Array, outbuf?: Float32Array): Float32Array { + const output = outbuf ? outbuf : new Float32Array(input.length); + const len = Math.min(input.length, output.length); + for (let i = 0; i < len; ++i) { + output[i] = input[i]! / 0x8000; + } + return output; +} + +/** + * Converts a Float32Array buffer to an Int16Array buffer. + * It intentionally doesn't clip values outside the -1.0 to 1.0 range + * because performance is more important in this context. + * @param input The input Float32Array buffer. + * @param outbuf The output Int16Array buffer (optional) + * if not provided, a new buffer will be created. + * @returns The converted Int16Array buffer. + */ +export function bufferF32ToI16(input: Float32Array, outbuf?: Int16Array): Int16Array { + const output = outbuf ? outbuf : new Int16Array(input.length); + const len = Math.min(input.length, output.length); + for (let i = 0; i < len; ++i) { + output[i] = input[i]! * 0x7fff; + } + return output; +} + +/** + * Clips the values in a Float32Array buffer to the -1.0 to 1.0 range. + * @param buffer The Float32Array buffer to clip. + * @param outbuf The output Float32Array buffer (optional) + * if not provided, the input buffer will be modified in place. + */ +export function clipF32Buffer(buffer: Float32Array, outbuf?: Float32Array): void { + const output = outbuf ? outbuf : buffer; + const len = buffer.length; + for (let i = 0; i < len; ++i) { + if (buffer[i]! > 1.0) { + output[i] = 1.0; + } else if (buffer[i]! < -1.0) { + output[i] = -1.0; + } + } +} diff --git a/packages/core/src/transform/index.ts b/packages/core/src/transform/index.ts new file mode 100644 index 0000000..96bfaee --- /dev/null +++ b/packages/core/src/transform/index.ts @@ -0,0 +1,2 @@ +// Re-exporting converter functions +export { bufferF32ToI16, bufferI16ToF32 } from './converters'; \ No newline at end of file diff --git a/packages/core/src/utils/circularbuffer.ts b/packages/core/src/utils/circularbuffer.ts new file mode 100644 index 0000000..c1d995e --- /dev/null +++ b/packages/core/src/utils/circularbuffer.ts @@ -0,0 +1,181 @@ +export class CircularBuffer { + [index: number]: T; + + protected _bufferLen: number; + protected _buffer: Array; + protected _bufferHead = 0; + protected _bufferTail = 0; + protected _bufferFull = false; + onChangeSize: () => void = () => void {}; + + constructor(len: number) { + this._bufferLen = len; + this._buffer = new Array(this._bufferLen); + + return new Proxy(this, { + set: function (target: CircularBuffer, name: PropertyKey, value: any) { + const index: number = (typeof name == 'string' ? parseInt(name) : NaN); + if (!isNaN(index)) { + target.set(index, value); + } else { + target[name as keyof CircularBuffer] = value; + } + return true; + }, + get: function (target: CircularBuffer, name: PropertyKey) { + const index: number = (typeof name == 'string' ? parseInt(name) : NaN); + if (!isNaN(index)) { + return target.get(index); + } + return target[name as keyof CircularBuffer]; + } + }); + } + + clear() { + this._bufferHead = 0; + this._bufferTail = 0; + this._bufferFull = false; + this.onChangeSize(); + } + + capacity() { + return this._bufferLen; + } + + size() { + if (this._bufferFull) + return this._bufferLen; + if (this._bufferHead <= this._bufferTail) + return this._bufferTail - this._bufferHead; + return this._bufferTail + this._bufferLen - this._bufferHead; + } + + getRawBuffer() { + return this._buffer; + } + + getHead() { + return this._bufferHead; + } + + getTail() { + return this._bufferTail; + } + + isFull() { + return this._bufferFull; + } + + isEmpty() { + return this._bufferHead == this._bufferTail && !this._bufferFull; + } + + checkIndex(index: number) { + if (this._bufferFull) + return (0 <= index && index < this._bufferLen); + if (this._bufferHead <= this._bufferTail) + return (this._bufferHead <= index && index < this._bufferTail); + return (0 <= index && index < this._bufferTail || this._bufferHead <= index && index < this._bufferLen); + } + + push_front(val: T) { + if (this._bufferFull) this.pop_back(); + if (--this._bufferHead < 0) this._bufferHead = this._bufferLen - 1; + this._bufferFull = this._bufferHead === this._bufferTail; + this._buffer[this._bufferHead] = val; + this.onChangeSize(); + } + + push_back(val: T) { + if (this._bufferFull) this.pop_front(); + this._buffer[this._bufferTail] = val; + this._bufferTail = (this._bufferTail + 1) % this._bufferLen; + this._bufferFull = this._bufferHead === this._bufferTail; + this.onChangeSize(); + } + + front() { + return this._buffer[this._bufferHead]; + } + + back() { + return this._buffer[this._bufferTail > 0 ? this._bufferTail - 1 : this._bufferLen - 1]; + } + + set(index: number, val: T) { + const tabIndex = (this._bufferHead + index) % this._bufferLen; + if (!this.checkIndex(tabIndex)) + throw new Error(`circularbuffer.set: index ${index} converted to table index ${tabIndex} out of range`); + this._buffer[tabIndex] = val; + } + + get(index: number) { + const tabIndex = (this._bufferHead + index) % this._bufferLen; + if (!this.checkIndex(tabIndex)) + throw new Error(`circularbuffer.get: index ${index} converted to table index ${tabIndex} out of range`); + return this._buffer[tabIndex]; + } + + pop_front(cnt: number = 1) { + if (cnt < 1) return; + const cntBiggerSize = (cnt >= this.size()); + this._bufferHead = (this._bufferHead + cnt) % this._bufferLen; + if (cntBiggerSize) + this._bufferTail = this._bufferHead; + this._bufferFull = false; + this.onChangeSize(); + } + + pop_back(cnt: number = 1) { + if (cnt < 1) return; + const cntBiggerSize = (cnt >= this.size()); + cnt = cnt % this._bufferLen; + if (this._bufferTail - cnt < 0) + this._bufferTail = this._bufferLen - (cnt - this._bufferTail); + else + this._bufferTail -= cnt; + if (cntBiggerSize) + this._bufferHead = this._bufferTail; + this._bufferFull = false; + this.onChangeSize(); + } + + alloc_front(cnt: number = 1) { + if (cnt < 1) return; + const free = this.capacity() - this.size(); + if (free < cnt) { + this.pop_back(cnt - free); + this._bufferHead = this._bufferTail; + } else { + if (this._bufferHead - cnt < 0) + this._bufferHead = this._bufferLen - (cnt - this._bufferHead); + else + this._bufferHead -= cnt; + } + this._bufferFull = this._bufferHead === this._bufferTail; + this.onChangeSize(); + } + + alloc_back(cnt: number = 1) { + if (cnt < 1) return; + const free = this.capacity() - this.size(); + if (free < cnt) { + this.pop_front(cnt - free); + this._bufferTail = this._bufferHead; + } else { + this._bufferTail = (this._bufferTail + cnt) % this._bufferLen + } + this._bufferFull = this._bufferHead === this._bufferTail + this.onChangeSize(); + } + + async waitForChangeSize(): Promise { + return new Promise(resolve => { + this.onChangeSize = function () { + resolve(); + this.onChangeSize = () => void {}; + } + }) + } +} diff --git a/packages/core/src/utils/convUtils.ts b/packages/core/src/utils/convUtils.ts new file mode 100644 index 0000000..129c7bc --- /dev/null +++ b/packages/core/src/utils/convUtils.ts @@ -0,0 +1,26 @@ +export function stringToBoolean(str: string | null | undefined): boolean { + try { + switch (str?.toLowerCase()?.trim()) { + case "true": + case "yes": + case "1": + return true; + + case "false": + case "no": + case "0": + case "": + case "null": + case "undefined": + case null: + case undefined: + return false; + + default: + throw new Error("Invalid input"); + } + } catch (err) { + console.error("stringToBoolean: error converting ", str, " to boolean"); + } + return false; +} diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts new file mode 100644 index 0000000..6ca32bc --- /dev/null +++ b/packages/core/src/utils/index.ts @@ -0,0 +1,10 @@ +// Re-export utility functions and types +export { CircularBuffer } from './circularbuffer'; +export { sleep, usleep, now, timestampToTimeString } from './time'; +export { PromiseHelper } from './promiseHelper'; +export { JournalLogLevel } from './journal'; +export type { JournalLogLevelKeys, JournalLogItem } from './journal'; +export { SimpleLogger, LogLevels as LOG_LEVELS } from './logger'; +export type { LoggerInterface, LogLevel } from './logger'; +export { stringToBoolean } from './convUtils'; +export { containsAnySubstr } from './stringUtils'; diff --git a/packages/core/src/utils/journal.ts b/packages/core/src/utils/journal.ts new file mode 100644 index 0000000..ab0f973 --- /dev/null +++ b/packages/core/src/utils/journal.ts @@ -0,0 +1,16 @@ +export enum JournalLogLevel { + DEBUG = 'DEBUG', // debugging information + INFO = 'INFO', // general information + WARNING = 'WARNING',// abnormal/unexpected condition + ERROR = 'ERROR', // error condition, requires user action + FATAL = 'FATAL', // fatal, program aborted +} + +export type JournalLogLevelKeys = keyof typeof JournalLogLevel; + +export interface JournalLogItem { + timestamp: number, + subSystem: string, + logLevel: JournalLogLevel, + message: string, +} diff --git a/packages/core/src/utils/logger.ts b/packages/core/src/utils/logger.ts new file mode 100644 index 0000000..6cf6af4 --- /dev/null +++ b/packages/core/src/utils/logger.ts @@ -0,0 +1,51 @@ +export const LogLevels = ["verbose", "debug", "log", "warn", "error", "fatal"] as const; + +export type LogLevel = (typeof LogLevels)[number]; + +export interface LoggerInterface { + log(message: any, ...optionalParams: any[]): void; + error(message: any, ...optionalParams: any[]): void; + warn(message: any, ...optionalParams: any[]): void; + debug?(message: any, ...optionalParams: any[]): void; + verbose?(message: any, ...optionalParams: any[]): void; + fatal?(message: any, ...optionalParams: any[]): void; +} + +export class SimpleLogger implements LoggerInterface { + protected context?: string | undefined; + + constructor(context?: string) { + this.context = context; + }; + + private constructMessage(message: string): string { + if (this.context !== undefined) { + return `[${this.context}] ${message}`; + } + return message; + } + + log(message: any, ...optionalParams: any[]) { + console.log(this.constructMessage(message), ...optionalParams); + } + + error(message: any, ...optionalParams: any[]) { + console.error(this.constructMessage(message), ...optionalParams); + } + + warn(message: any, ...optionalParams: any[]) { + console.warn(this.constructMessage(message), ...optionalParams); + } + + debug(message: any, ...optionalParams: any[]) { + console.debug(this.constructMessage(message), ...optionalParams); + } + + verbose(message: any, ...optionalParams: any[]) { + console.info(this.constructMessage(message), ...optionalParams); + } + + fatal(message: any, ...optionalParams: any[]) { + console.error(this.constructMessage(message), ...optionalParams); + } +} diff --git a/packages/core/src/utils/promiseHelper.ts b/packages/core/src/utils/promiseHelper.ts new file mode 100644 index 0000000..fd20b9d --- /dev/null +++ b/packages/core/src/utils/promiseHelper.ts @@ -0,0 +1,37 @@ +export class PromiseHelper { + protected _promiseMap: Map> = new Map>(); + protected _promiseMapId: number = 0; + + createPromise(): [number, Promise] { + ++this._promiseMapId; + return [ + this._promiseMapId, + new Promise((resolve, reject) => { + this._promiseMap.set(this._promiseMapId, { resolve: resolve, reject: reject }); + }) + ]; + } + + clear() { + this._promiseMap = new Map>(); + this._promiseMapId = 0; + } + + getPromise(id: number) { + return this._promiseMap.get(id); + } + + deletePromise(id: number) { + return this._promiseMap.delete(id); + } + + promiseResolve(promise: Record, res?: any) { + if (promise && promise.resolve) + promise.resolve(res); + } + + promiseReject(promise: Record, res?: any) { + if (promise && promise.reject) + promise.reject(res); + } +} \ No newline at end of file diff --git a/packages/core/src/utils/stringUtils.ts b/packages/core/src/utils/stringUtils.ts new file mode 100644 index 0000000..3132438 --- /dev/null +++ b/packages/core/src/utils/stringUtils.ts @@ -0,0 +1,12 @@ +export function containsAnySubstr(str: string | undefined | null, substrs: string | Array | undefined | null, caseSensitive = false): boolean { + if (!str || !substrs) return false; + const text = caseSensitive ? str : str.toLowerCase(); + if (!Array.isArray(substrs)) substrs = [substrs]; + for (const substr of substrs) { + if (!substr) continue; + const substrConv = caseSensitive ? substr : substr.toLowerCase().trim(); + if (substrConv === '') continue; + if (text.includes(substrConv)) return true; + } + return false; +} \ No newline at end of file diff --git a/packages/core/src/utils/time.ts b/packages/core/src/utils/time.ts new file mode 100644 index 0000000..d97ddaa --- /dev/null +++ b/packages/core/src/utils/time.ts @@ -0,0 +1,19 @@ +export function sleep(s: number) { + return new Promise(r => setTimeout(r, s * 1000)); +} + +export function usleep(s: number) { + return new Promise(r => setTimeout(r, s)); +} + +export function now() { + return performance.now(); +} + +export function timestampToTimeString(timestamp: number) { + const date = new Date(timestamp); + return date.getHours().toString().padStart(2, '0') + + ':' + date.getMinutes().toString().padStart(2, '0') + + ':' + date.getSeconds().toString().padStart(2, '0') + + '.' + date.getMilliseconds().toString().toString().padStart(3, '0'); +} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000..a207c93 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,54 @@ +{ + "compilerOptions": { + // File Layout + "rootDir": "src", + "outDir": "dist", + // Path Mapping + "baseUrl": ".", + "paths": { + "@/*": [ + "src/*" + ], + "vitest": [ + "../../node_modules/vitest" + ] + }, + // Environment Settings + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "types": [ + "vitest/globals" + ], + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + // "exactOptionalPropertyTypes": true, + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + // Recommended Options + "strict": true, + "esModuleInterop": true, + "jsx": "preserve", + "verbatimModuleSyntax": true, + // "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts new file mode 100644 index 0000000..e9cedd4 --- /dev/null +++ b/packages/core/vitest.config.ts @@ -0,0 +1,31 @@ +import { defineConfig } from 'vitest/config'; +import path from 'path'; + +export default defineConfig({ + test: { + name: 'core', + globals: true, + environment: 'node', + include: ['src/tests/**/*.test.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + exclude: [ + 'node_modules/**', + 'dist/**', + 'build/**', + 'old/**', + '**/tests/', + '*.config.*', + '**/index.ts', + '**/*.d.ts' + ], + reportsDirectory: './coverage' + }, + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + } +}); diff --git a/packages/frontend-core/build_control.sh b/packages/frontend-core/build_control.sh new file mode 100755 index 0000000..822501a --- /dev/null +++ b/packages/frontend-core/build_control.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Script to build control library WebAssembly module using Emscripten + +BUILD_SCRIPT="../../scripts/build_wasm_library.sh" + +NEED_CLONE=1 # 0 - no clone, 1 - git clone, 2 - copy from $LOCAL_LIB_PATH +NEED_BUILD=2 # 0 - no build, 1 - build, 2 - rebuild + +LIB_NAME="control" +LIB_GIT_REPO="https://github.com/wavelet-lab/usdr-lib.git" +LIB_GIT_BRANCH="main" +LIB_CMAKE_PATH="src/lib/webusb/emlib" + +EXPORTED_RUNTIME_METHODS="HEAPF32,HEAPU32,HEAP32,HEAPU16,HEAP16,HEAPU8,HEAP8,ccall,cwrap,stringToAscii,AsciiToString" +ASYNCIFY_IMPORTS="write_ep1,write_ep2,read_ep1,read_ep2,write_log_js" + +set -eo pipefail + +# call the generic build script with the parameters +NEED_CLONE=${NEED_CLONE} \ +NEED_BUILD=${NEED_BUILD} \ +LIB_NAME=${LIB_NAME} \ +LIB_GIT_REPO=${LIB_GIT_REPO} \ +LIB_GIT_BRANCH=${LIB_GIT_BRANCH} \ +LIB_CMAKE_PATH=${LIB_CMAKE_PATH} \ +EXPORTED_RUNTIME_METHODS=${EXPORTED_RUNTIME_METHODS} \ +ASYNCIFY_IMPORTS=${ASYNCIFY_IMPORTS} \ +${BUILD_SCRIPT} diff --git a/packages/frontend-core/package.json b/packages/frontend-core/package.json new file mode 100644 index 0000000..62fecbc --- /dev/null +++ b/packages/frontend-core/package.json @@ -0,0 +1,54 @@ +{ + "name": "@websdr/frontend-core", + "version": "1.0.0", + "description": "This is the core frontend package for WebSDR", + "author": "Timur ", + "license": "MIT", + "keywords": [], + "main": "dist/index.js", + "types": "dist/index.d.ts", + "type": "module", + "scripts": { + "build": "tsc && tsc-alias && tsc-esm-fix", + "prebuild": "node ./scripts/prebuild.js", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" + }, + "dependencies": { + "@websdr/core": "^1.0.0", + "usb": "^2.16.0" + }, + "devDependencies": { + "@types/emscripten": "^1.41.5", + "@types/node": "^24.10.0", + "@types/w3c-web-usb": "^1.0.13" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.js" + }, + "./common": { + "types": "./dist/common/index.d.ts", + "import": "./dist/common/index.js", + "require": "./dist/common/index.js" + }, + "./control": { + "types": "./dist/control/control.d.ts", + "import": "./dist/control/control.js", + "require": "./dist/control/control.js" + }, + "./services": { + "types": "./dist/services/index.d.ts", + "import": "./dist/services/index.js", + "require": "./dist/services/index.js" + }, + "./webusb": { + "types": "./dist/webusb/index.d.ts", + "import": "./dist/webusb/index.js", + "require": "./dist/webusb/index.js" + } + } +} diff --git a/packages/frontend-core/scripts/prebuild.js b/packages/frontend-core/scripts/prebuild.js new file mode 100644 index 0000000..c1c4ca9 --- /dev/null +++ b/packages/frontend-core/scripts/prebuild.js @@ -0,0 +1,31 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const pkgRoot = path.resolve(__dirname, '..'); + +function copyDir(src, dest, skipFiles = []) { + if (!fs.existsSync(src)) return; + fs.mkdirSync(dest, { recursive: true }); + for (const item of fs.readdirSync(src)) { + const s = path.join(src, item); + const d = path.join(dest, item); + if (fs.statSync(s).isDirectory()) copyDir(s, d, skipFiles); + else if (!skipFiles.includes(item)) fs.copyFileSync(s, d); + } +} + + +(function main() { + try { + console.log('Copying control files...'); + copyDir(path.join(pkgRoot, 'src', 'control'), path.join(pkgRoot, 'dist', 'control'), ['pre.js']); + process.exit(0); + } catch (e) { + console.error(e); + process.exit(1); + } +})(); diff --git a/packages/frontend-core/src/common/debug.ts b/packages/frontend-core/src/common/debug.ts new file mode 100644 index 0000000..6a0f54d --- /dev/null +++ b/packages/frontend-core/src/common/debug.ts @@ -0,0 +1,22 @@ +declare global { + var debug_mode: boolean; + + interface ImportMeta { + readonly env?: ImportMetaEnv; + } +} + +interface ImportMetaEnv { + readonly VITE_DEBUG?: string; +} + +// compute once and assign +const _debug_mode = ((import.meta.env?.VITE_DEBUG) ?? (typeof process !== 'undefined' ? process.env?.VITE_DEBUG : undefined)) === 'true'; + +// only set if not already set to avoid overwriting other code during tests +if (typeof globalThis.debug_mode !== 'boolean') { + globalThis.debug_mode = _debug_mode; +} else { + // keep existing value (no-op) +} +export const debug_mode = globalThis.debug_mode; diff --git a/packages/frontend-core/src/common/index.ts b/packages/frontend-core/src/common/index.ts new file mode 100644 index 0000000..73e4927 --- /dev/null +++ b/packages/frontend-core/src/common/index.ts @@ -0,0 +1,4 @@ +// Re-exporting all common functionalities +export { debug_mode } from "./debug"; +export { Protocol, NngWebSocket } from "./nngWebSocket"; +export { WASMErrno } from "./wasmErrno"; diff --git a/packages/frontend-core/src/common/nngWebSocket.ts b/packages/frontend-core/src/common/nngWebSocket.ts new file mode 100644 index 0000000..a8cb138 --- /dev/null +++ b/packages/frontend-core/src/common/nngWebSocket.ts @@ -0,0 +1,384 @@ +import { sleep } from '@websdr/core/utils'; + +const nngVerbose = false; + +enum BinaryType { + BLOB = 0, + ARRAYBUFFER = 1, + TEXT = 2, +} + +const REQ_ID_HEADER_LEN = 4; +const REQ_ID_FLAG_BIT = 1 << 7; +const REQ_ID_MAX = 0x7fffffff; +const REQ_ID_MOD = 0x80000000; +const DEFAULT_RECONNECT_TIME = 5; +const DEFAULT_SEND_TIMEOUT_MS = 30000; + +// see the rfc on sp websocket mapping: +// raw.githubusercontent.com/nanomsg/nanomsg/master/rfc/sp-websocket-mapping-01.txt +export const Protocol = { + UNKNOWN: '', + REQ: 'rep.sp.nanomsg.org', + SUB: 'pub.sp.nanomsg.org', + PUB: 'sub.sp.nanomsg.org', +} + +export class NngWebSocket extends EventTarget { + static BLOB = BinaryType.BLOB; + static ARRAYBUFFER = BinaryType.ARRAYBUFFER; + static TEXT = BinaryType.TEXT; + static reqIdHeaderLen = REQ_ID_HEADER_LEN; + static RECONNECT_TIME = DEFAULT_RECONNECT_TIME; + + protected _url: string; + protected _binaryType: number; + protected _protocol: string; + protected _closing: boolean; + protected _websocket: WebSocket | undefined; + protected _onOpen: (event: Event) => Promise; + protected _onClose: (event: CloseEvent) => Promise; + protected _onMessage: (event: MessageEvent) => Promise; + protected _onError: (event: Event) => Promise; + protected _open_promise: + { + resolve: ((value: NngWebSocket | PromiseLike) => void) | undefined, + reject: ((reason?: any) => void) | undefined, + } + | undefined; + protected _send_promises: { + [reqId: number]: { + resolve: ((value: any) => void) | undefined, + reject: ((reason?: any) => void) | undefined, + } + }; + protected _reqId: number; + protected _reconnectTime: number; + + constructor(parms: { url?: string, binaryType?: number, protocol?: string, reconnectTime?: number }) { + super(); + this._url = parms.url ?? ''; + // use enum default + this._binaryType = parms.binaryType ?? BinaryType.ARRAYBUFFER; + this._protocol = parms.protocol ?? Protocol.SUB; + this._closing = true; + this._websocket = undefined; + this._onOpen = this.onWsOpen.bind(this); + this._onClose = this.onWsClose.bind(this); + this._onMessage = this.onWsMessage.bind(this); + this._onError = this.onWsError.bind(this); + this._open_promise = undefined; + this._send_promises = {}; + this._reqId = Math.floor(Math.random() * REQ_ID_MAX); + this._reconnectTime = parms.reconnectTime ?? NngWebSocket.RECONNECT_TIME; + } + + get url() { + return this._url; + } + + isConnecting() { + return this._websocket !== undefined && this._websocket.readyState === WebSocket.CONNECTING + } + + isConnected() { + return this._websocket !== undefined && this._websocket.readyState === WebSocket.OPEN + } + + // helper: map internal binaryType to WebSocket.binaryType value + protected wsBinaryTypeValue(): 'arraybuffer' | 'blob' { + // TEXT handled as arraybuffer (we still decode to string on receive) + return this._binaryType === NngWebSocket.BLOB ? 'blob' : 'arraybuffer'; + } + + async open(url = undefined, binaryType = undefined, protocol = undefined) { + if (url !== undefined) this._url = url + if (binaryType !== undefined) this._binaryType = binaryType + if (protocol !== undefined) this._protocol = protocol + + let promise = new Promise((resolve, reject) => { + this._open_promise = { resolve, reject } + }) + if (!this.isConnecting()) { + try { + // ensure previous socket fully closed before creating a new one + await this.close(); + this._closing = false; + if (this._protocol !== undefined && this._protocol !== Protocol.UNKNOWN) { + this._websocket = new WebSocket(this._url, this._protocol); + } else { + this._websocket = new WebSocket(this._url); + } + // set only allowed values for binaryType + if (this._websocket && typeof this._websocket.binaryType === 'string') { + this._websocket.binaryType = this.wsBinaryTypeValue(); + } + + this._websocket.addEventListener('open', this._onOpen); + this._websocket.addEventListener('close', this._onClose); + this._websocket.addEventListener('message', this._onMessage); + this._websocket.addEventListener('error', this._onError); + } catch (err) { + if (this._open_promise) { + this._open_promise.reject?.('NngWebSocket: ' + String(err)); + this._open_promise = undefined; + } + } + } else if (this.isConnected()) { + if (this._open_promise) { + this._open_promise.resolve?.(this); + this._open_promise = undefined; + } + } + + return promise; + } + + async close() { + if (globalThis.debug_mode || nngVerbose) + console.log(`NngWebSocket: close connection to url ${this._url}`); + this._closing = true + if (this._websocket !== undefined) { + // reject all outstanding send promises to avoid leaks + for (const k of Object.keys(this._send_promises)) { + this._send_promises[Number(k)]!.reject?.(new Error('NngWebSocket: connection closed')); + delete this._send_promises[Number(k)]; + } + this._websocket.removeEventListener('open', this._onOpen); + this._websocket.removeEventListener('close', this._onClose); + this._websocket.removeEventListener('message', this._onMessage); + this._websocket.removeEventListener('error', this._onError); + this._websocket.close(); + this._websocket = undefined; + this._open_promise = undefined; + this._send_promises = {}; + } + } + + encodeReqIdHeader(reqId: number) { + let buf = new Uint8Array(NngWebSocket.reqIdHeaderLen); + buf[0] = ((reqId >> 24) & 0xff) | REQ_ID_FLAG_BIT; + buf[1] = (reqId >> 16) & 0xff; + buf[2] = (reqId >> 8) & 0xff; + buf[3] = reqId & 0xff; + return buf; + } + + decodeReqIdHeader(reqIdHeader: ArrayBuffer) { + const view = new Uint8Array(reqIdHeader); + if (view.length !== NngWebSocket.reqIdHeaderLen) return -1; + // use arithmetic to avoid sign/bitshift issues + return ((view[0]! & ~REQ_ID_FLAG_BIT) * 16777216) + (view[1]! * 65536) + (view[2]! * 256) + view[3]!; + } + + incReqId() { + this._reqId = (this._reqId + 1) % REQ_ID_MOD; + } + + async send(data: string | ArrayBuffer | Uint8Array | Blob, timeoutMs = DEFAULT_SEND_TIMEOUT_MS): Promise { + if (!this.isConnected()) { + return Promise.reject(`NngWebSocket: cannot send data, websocket to url ${this._url} is not connected`) + } + if (this._protocol === Protocol.REQ) { + this.incReqId() + const reqIdHeader = this.encodeReqIdHeader(this._reqId) + let enc_data: Uint8Array; + if (this._binaryType == NngWebSocket.TEXT) { + const encoder = new TextEncoder() + if (typeof data === 'string') { + enc_data = encoder.encode(data) + } else if (data instanceof Uint8Array) { + enc_data = data + } else if (data instanceof ArrayBuffer) { + enc_data = new Uint8Array(data) + } else if (typeof Blob !== 'undefined' && data instanceof Blob) { + const ab = await data.arrayBuffer() + enc_data = new Uint8Array(ab) + } else { + enc_data = encoder.encode(String(data)) + } + } else if (data instanceof Uint8Array) { + enc_data = data + } else if (data instanceof ArrayBuffer) { + enc_data = new Uint8Array(data) + } else if (typeof Blob !== 'undefined' && data instanceof Blob) { + const ab = await data.arrayBuffer() + enc_data = new Uint8Array(ab) + } else { + // fallback: stringify + enc_data = new TextEncoder().encode(String(data)) + } + + const buf_len = enc_data.length + reqIdHeader.length + let buf = new Uint8Array(buf_len) + buf.set(reqIdHeader) + buf.set(enc_data, reqIdHeader.length) + + this._websocket!.send(buf) + + return new Promise((resolve, reject) => { + const reqId = this._reqId; + this._send_promises[reqId] = { resolve, reject } + + // add timeout cleanup to avoid leaking promises + const timer = setTimeout(() => { + if (this._send_promises[reqId]) { + this._send_promises[reqId]!.reject?.(`NngWebSocket: request ${reqId} timed out`); + delete this._send_promises[reqId]; + } + }, timeoutMs); + + // wrap resolve/reject to clear timeout + const orig = this._send_promises[reqId]!; + this._send_promises[reqId] = { + resolve: (v: any) => { clearTimeout(timer); orig.resolve?.(v) }, + reject: (e: any) => { clearTimeout(timer); orig.reject?.(e) } + } + }) + } + this._websocket!.send(data) + return Promise.resolve() + } + + receive(event: MessageEvent) { + let reqId = -1; + let buf: string | Uint8Array | ArrayBuffer | Blob | null = null; + + // Prefer explicit checks in order: ArrayBuffer -> Blob -> string/other + if (event.data instanceof ArrayBuffer) { + if (this._protocol === Protocol.REQ) { + reqId = this.decodeReqIdHeader(event.data.slice(0, NngWebSocket.reqIdHeaderLen)); + const payload = event.data.slice(NngWebSocket.reqIdHeaderLen); + if (this._binaryType == NngWebSocket.TEXT) { + buf = new TextDecoder('utf-8').decode(payload); + } else { + buf = payload; + } + } else { + if (this._binaryType == NngWebSocket.TEXT) { + buf = new TextDecoder('utf-8').decode(new Uint8Array(event.data)); + } else { + buf = event.data; + } + } + } else if (typeof Blob !== 'undefined' && event.data instanceof Blob) { + // Blob handling + if (this._protocol === Protocol.REQ) { + // read header then payload + // convert to ArrayBuffer once (async would be needed, but here we keep behavior similar to previous impl) + // prefer converting to ArrayBuffer synchronously not possible — handle in onWsMessage if needed + // for now keep blob as-is + buf = event.data; + } else { + buf = event.data; + } + } else if (typeof event.data === 'string') { + buf = event.data; + } + + if (globalThis.debug_mode || nngVerbose) { + let len = -1; + if (buf != null) { + if (typeof buf === 'string') { + len = buf.length; + } else if (buf instanceof ArrayBuffer) { + len = buf.byteLength; + } else if (buf instanceof Uint8Array) { + len = buf.length; + } else if (typeof Blob !== 'undefined' && buf instanceof Blob) { + len = buf.size; + } + } + console.log(`NngWebSocket: Received message from url ${this._url}, len = ${len}`); + } + + if (reqId >= 0 && this._send_promises[reqId]) { + if (buf) { + this._send_promises[reqId]!.resolve?.(buf); + } else { + this._send_promises[reqId]!.reject?.(`NngWebSocket: Received an empty buffer from ${this._url}`); + } + delete this._send_promises[reqId]; + } + + return buf; + } + + async onWsOpen(event: Event) { + if (globalThis.debug_mode || nngVerbose) + console.log(`NngWebSocket: connection to url ${this._url} established`); + if (this._open_promise) { + if (this._open_promise.resolve) + this._open_promise.resolve(this); + this._open_promise = undefined; + } + + this.dispatchEvent(event); + } + + async onWsClose(event: CloseEvent) { + if (event.wasClean && this._closing) { + if (globalThis.debug_mode || nngVerbose) + console.log(`NngWebSocket: connection to url ${this._url} has been closed`); + } else { + const err_str = `NngWebSocket: connection to url ${this._url} has been droped... reopening`; + if (this._open_promise) { + this._open_promise.reject?.(err_str); + this._open_promise = undefined; + } else if (globalThis.debug_mode || nngVerbose) console.error(err_str); + + if (this._reconnectTime !== -1) { + await sleep(this._reconnectTime); + this.open().catch(() => { }); + } + } + + // dispatch the actual CloseEvent so listeners get reason/code/etc. + this.dispatchEvent(event); + } + + async onWsMessage(event: MessageEvent) { + if (globalThis.debug_mode || nngVerbose) + console.log('NngWebSocket: receive event', event); + // if we received a Blob, convert it to ArrayBuffer first so receive() can parse header + if (typeof Blob !== 'undefined' && event.data instanceof Blob) { + const ab = await event.data.arrayBuffer(); + const converted = new MessageEvent('message', { data: ab }); + const buf = this.receive(converted); + if (buf) this.dispatchEvent(new CustomEvent('data', { detail: { data: buf } })); + this.dispatchEvent(converted); + return; + } + const buf = this.receive(event); + if (buf) this.dispatchEvent(new CustomEvent('data', { detail: { data: buf } })); + this.dispatchEvent(event); + } + + async onWsError(event: Event) { + let detailStr = ''; + if ((event as any).data !== undefined) { + detailStr = String((event as any).data); + } else if (event instanceof ErrorEvent) { + detailStr = event.message; + } else { + try { + detailStr = JSON.stringify(event); + } catch { + detailStr = String(event); + } + } + const err_str = `NngWebSocket: connection to url ${this._url}: an error has occurred: ` + detailStr; + if (globalThis.debug_mode || nngVerbose) console.error(err_str, event); + + if (this._open_promise) { + this._open_promise.reject?.(err_str); + this._open_promise = undefined; + } + // reject all outstanding send promises (not only current reqId) + for (const k of Object.keys(this._send_promises)) { + this._send_promises[Number(k)]!.reject?.(err_str); + delete this._send_promises[Number(k)]; + } + this.dispatchEvent(event); + } +} diff --git a/packages/frontend-core/src/common/wasmErrno.ts b/packages/frontend-core/src/common/wasmErrno.ts new file mode 100644 index 0000000..1c2705c --- /dev/null +++ b/packages/frontend-core/src/common/wasmErrno.ts @@ -0,0 +1,122 @@ +export enum WASMErrno { + ESUCCESS = 0, + E2BIG = -1, + EACCES = -2, + EADDRINUSE = -3, + EADDRNOTAVAIL = -4, + EAFNOSUPPORT = -5, + EAGAIN = -6, + EALREADY = -7, + EBADF = -8, + EBADMSG = -9, + EBUSY = -10, + ECANCELED = -11, + ECHILD = -12, + ECONNABORTED = -13, + ECONNREFUSED = -14, + ECONNRESET = -15, + EDEADLOCK = -16, + EDESTADDRREQ = -17, + EDOM = -18, + EDQUOT = -19, + EEXIST = -20, + EFAULT = -21, + EFBIG = -22, + EHOSTUNREACH = -23, + EIDRM = -24, + EILSEQ = -25, + EINPROGRESS = -26, + EINTR = -27, + EINVAL = -28, + EIO = -29, + EISCONN = -30, + EISDIR = -31, + ELOOP = -32, + EMFILE = -33, + EMLINK = -34, + EMSGSIZE = -35, + EMULTIHOP = -36, + ENAMETOOLONG = -37, + ENETDOWN = -38, + ENETRESET = -39, + ENETUNREACH = -40, + ENFILE = -41, + ENOBUFS = -42, + ENODEV = -43, + ENOENT = -44, + ENOEXEC = -45, + ENOLCK = -46, + ENOLINK = -47, + ENOMEM = -48, + ENOMSG = -49, + ENOPROTOOPT = -50, + ENOSPC = -51, + ENOSYS = -52, + ENOTCONN = -53, + ENOTDIR = -54, + ENOTEMPTY = -55, + ENOTRECOVERABLE = -56, + ENOTSOCK = -57, + ENOTTY = -59, + ENXIO = -60, + EOVERFLOW = -61, + EOWNERDEAD = -62, + EPERM = -63, + EPIPE = -64, + EPROTO = -65, + EPROTONOSUPPORT = -66, + EPROTOTYPE = -67, + ERANGE = -68, + EROFS = -69, + ESPIPE = -70, + ESRCH = -71, + ESTALE = -72, + EUNATCH = -73, + ETXTBSY = -74, + EXDEV = -75, + ENOSTR = -100, + EBFONT = -101, + EBADSLT = -102, + EBADRQC = -103, + ENOANO = -104, + ENOTBLK = -105, + ECHRNG = -106, + EL3HLT = -107, + EL3RST = -108, + ELNRNG = -109, + ETIMEDOUT = -110, + ENOCSI = -111, + EL2HLT = -112, + EBADE = -113, + EBADR = -114, + EXFULL = -115, + ENODATA = -116, + ETIME = -117, + ENOSR = -118, + ENONET = -119, + ENOPKG = -120, + EREMOTE = -121, + EADV = -122, + ESRMNT = -123, + ECOMM = -124, + EDOTDOT = -125, + ENOTUNIQ = -126, + EBADFD = -127, + EREMCHG = -128, + ELIBACC = -129, + ELIBBAD = -130, + ELIBSCN = -131, + ELIBMAX = -132, + ELIBEXEC = -133, + ESTRPIPE = -135, + EUSERS = -136, + ESOCKTNOSUPPORT = -137, + ENOTSUP = -138, + EOPNOTSUPP = -138, + EPFNOSUPPORT = -139, + ESHUTDOWN = -140, + ETOOMANYREFS = -141, + EHOSTDOWN = -142, + ENOMEDIUM = -148, + EL2NSYNC = -156, +}; diff --git a/packages/frontend-core/src/control/control.d.ts b/packages/frontend-core/src/control/control.d.ts new file mode 100644 index 0000000..1a1ea52 --- /dev/null +++ b/packages/frontend-core/src/control/control.d.ts @@ -0,0 +1,16 @@ +export interface ControlModule extends EmscriptenModule { + _init_lib(device: number, vid: number, pid: number): number; + _close_device(fd: number): number; + _send_command(fd: number, cmd: number, cmd_len: number, res: number, res_len: number): number; + _send_debug_command(fd: number, cmd: number, cmd_len: number, res: number, res_len: number): number; + ccall(func: string, ret_type: string, parm_types: Array, parms: Array, opts: Record): any; + AsciiToString(buf: number): string; + stringToAscii(str: string, buf: number): void; +} + +declare const Module: EmscriptenModuleFactory; +declare global { + var controlModule: ControlModule | undefined; +} + +export default Module; \ No newline at end of file diff --git a/packages/frontend-core/src/control/control.js b/packages/frontend-core/src/control/control.js new file mode 100644 index 0000000..618d48a --- /dev/null +++ b/packages/frontend-core/src/control/control.js @@ -0,0 +1,6 @@ +// @ts-nocheck +/* eslint-disable */ + +import { write_ep1, write_ep2, read_ep1, read_ep2, write_log_js } from '@/webusb/webUsbControlApi'; +async function Module(moduleArg={}){var moduleRtn;(function(){function humanReadableVersionToPacked(str){str=str.split("-")[0];var vers=str.split(".").slice(0,3);while(vers.length<3)vers.push("00");vers=vers.map((n,i,arr)=>n.padStart(2,"0"));return vers.join("")}var packedVersionToHumanReadable=n=>[n/1e4|0,(n/100|0)%100,n%100].join(".");var TARGET_NOT_SUPPORTED=2147483647;var currentNodeVersion=typeof process!=="undefined"&&process?.versions?.node?humanReadableVersionToPacked(process.versions.node):TARGET_NOT_SUPPORTED;if(currentNodeVersion<16e4){throw new Error(`This emscripten-generated code requires node v${packedVersionToHumanReadable(16e4)} (detected v${packedVersionToHumanReadable(currentNodeVersion)})`)}var currentSafariVersion=typeof navigator!=="undefined"&&navigator?.userAgent?.includes("Safari/")&&navigator.userAgent.match(/Version\/(\d+\.?\d*\.?\d*)/)?humanReadableVersionToPacked(navigator.userAgent.match(/Version\/(\d+\.?\d*\.?\d*)/)[1]):TARGET_NOT_SUPPORTED;if(currentSafariVersion<15e4){throw new Error(`This emscripten-generated code requires Safari v${packedVersionToHumanReadable(15e4)} (detected v${currentSafariVersion})`)}var currentFirefoxVersion=typeof navigator!=="undefined"&&navigator?.userAgent?.match(/Firefox\/(\d+(?:\.\d+)?)/)?parseFloat(navigator.userAgent.match(/Firefox\/(\d+(?:\.\d+)?)/)[1]):TARGET_NOT_SUPPORTED;if(currentFirefoxVersion<79){throw new Error(`This emscripten-generated code requires Firefox v79 (detected v${currentFirefoxVersion})`)}var currentChromeVersion=typeof navigator!=="undefined"&&navigator?.userAgent?.match(/Chrome\/(\d+(?:\.\d+)?)/)?parseFloat(navigator.userAgent.match(/Chrome\/(\d+(?:\.\d+)?)/)[1]):TARGET_NOT_SUPPORTED;if(currentChromeVersion<85){throw new Error(`This emscripten-generated code requires Chrome v85 (detected v${currentChromeVersion})`)}})();var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;if(ENVIRONMENT_IS_NODE){const{createRequire}=await import("module");var require=createRequire(import.meta.url)}var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName=import.meta.url;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){const isNode=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";if(!isNode)throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");var fs=require("fs");if(_scriptName.startsWith("file:")){scriptDirectory=require("path").dirname(require("url").fileURLToPath(_scriptName))+"/"}readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);assert(Buffer.isBuffer(ret));return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");assert(binary?Buffer.isBuffer(ret):typeof ret=="string");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_SHELL){}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}if(!(globalThis.window||globalThis.WorkerGlobalScope))throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{throw new Error("environment detection error")}var out=console.log.bind(console);var err=console.error.bind(console);assert(!ENVIRONMENT_IS_SHELL,"shell environment detected but not enabled at build time. Add `shell` to `-sENVIRONMENT` to enable.");var wasmBinary;if(!globalThis.WebAssembly){err("no native wasm support detected")}var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort("Assertion failed"+(text?": "+text:""))}}var isFileURI=filename=>filename.startsWith("file://");function writeStackCookie(){var max=_emscripten_stack_get_end();assert((max&3)==0);if(max==0){max+=4}HEAPU32[max>>2]=34821223;HEAPU32[max+4>>2]=2310721022;HEAPU32[0>>2]=1668509029}function checkStackCookie(){if(ABORT)return;var max=_emscripten_stack_get_end();if(max==0){max+=4}var cookie1=HEAPU32[max>>2];var cookie2=HEAPU32[max+4>>2];if(cookie1!=34821223||cookie2!=2310721022){abort(`Stack overflow! Stack cookie has been overwritten at ${ptrToString(max)}, expected hex dwords 0x89BACDFE and 0x2135467, but received ${ptrToString(cookie2)} ${ptrToString(cookie1)}`)}if(HEAPU32[0>>2]!=1668509029){abort("Runtime error: The application has corrupted its heap memory area (address zero)!")}}var runtimeDebug=true;(()=>{var h16=new Int16Array(1);var h8=new Int8Array(h16.buffer);h16[0]=25459;if(h8[0]!==115||h8[1]!==99)abort("Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)")})();function consumedModuleProp(prop){if(!Object.getOwnPropertyDescriptor(Module,prop)){Object.defineProperty(Module,prop,{configurable:true,set(){abort(`Attempt to set \`Module.${prop}\` after it has already been processed. This can happen, for example, when code is injected via '--post-js' rather than '--pre-js'`)}})}}function makeInvalidEarlyAccess(name){return()=>assert(false,`call to '${name}' via reference taken before Wasm module initialization`)}function ignoredModuleProp(prop){if(Object.getOwnPropertyDescriptor(Module,prop)){abort(`\`Module.${prop}\` was supplied but \`${prop}\` not included in INCOMING_MODULE_JS_API`)}}function isExportedByForceFilesystem(name){return name==="FS_createPath"||name==="FS_createDataFile"||name==="FS_createPreloadedFile"||name==="FS_preloadFile"||name==="FS_unlink"||name==="addRunDependency"||name==="FS_createLazyFile"||name==="FS_createDevice"||name==="removeRunDependency"}function missingLibrarySymbol(sym){unexportedRuntimeSymbol(sym)}function unexportedRuntimeSymbol(sym){if(!Object.getOwnPropertyDescriptor(Module,sym)){Object.defineProperty(Module,sym,{configurable:true,get(){var msg=`'${sym}' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the Emscripten FAQ)`;if(isExportedByForceFilesystem(sym)){msg+=". Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you"}abort(msg)}})}}var readyPromiseResolve,readyPromiseReject;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}assert(globalThis.Int32Array&&globalThis.Float64Array&&Int32Array.prototype.subarray&&Int32Array.prototype.set,"JS engine does not provide full typed array support");function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}consumedModuleProp("preRun");callRuntimeCallbacks(onPreRuns)}function initRuntime(){assert(!runtimeInitialized);runtimeInitialized=true;checkStackCookie();if(!Module["noFSInit"]&&!FS.initialized)FS.init();TTY.init();wasmExports["__wasm_call_ctors"]();FS.ignorePermissions=false}function postRun(){checkStackCookie();if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}consumedModuleProp("postRun");callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;if(what.indexOf("RuntimeError: unreachable")>=0){what+='. "unreachable" may be due to ASYNCIFY_STACK_SIZE not being large enough (try increasing it)'}var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}function createExportWrapper(name,nargs){return(...args)=>{assert(runtimeInitialized,`native function \`${name}\` called before runtime initialization`);var f=wasmExports[name];assert(f,`exported native function \`${name}\` not found`);assert(args.length<=nargs,`native function \`${name}\` called with ${args.length} args but expects ${nargs}`);return f(...args)}}var wasmBinaryFile;function findWasmBinary(){if(Module["locateFile"]){return locateFile("control.wasm")}return new URL("control.wasm",import.meta.url).href}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);if(isFileURI(binaryFile)){err(`warning: Loading from a file URI (${binaryFile}) is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing`)}abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){Asyncify.instrumentWasmImports(wasmImports);var imports={env:wasmImports,wasi_snapshot_preview1:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;wasmExports=Asyncify.instrumentWasmExports(wasmExports);assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}var trueModule=Module;function receiveInstantiationResult(result){assert(Module===trueModule,"the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?");trueModule=null;return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{try{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})}catch(e){err(`Module.instantiateWasm callback failed with error: ${e}`);reject(e)}})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var dynCalls={};var dynCallLegacy=(sig,ptr,args)=>{sig=sig.replace(/p/g,"i");assert(sig in dynCalls,`bad function pointer type - sig is not in dynCalls: '${sig}'`);if(args?.length){assert(args.length===sig.length-1)}else{assert(sig.length==1)}var f=dynCalls[sig];return f(ptr,...args)};var noExitRuntime=true;var ptrToString=ptr=>{assert(typeof ptr==="number",`ptrToString expects a number, got ${typeof ptr}`);ptr>>>=0;return"0x"+ptr.toString(16).padStart(8,"0")};var stackRestore=val=>__emscripten_stack_restore(val);var stackSave=()=>_emscripten_stack_get_current();var warnOnce=text=>{warnOnce.shown||={};if(!warnOnce.shown[text]){warnOnce.shown[text]=1;if(ENVIRONMENT_IS_NODE)text="warning: "+text;err(text)}};var __abort_js=()=>abort("native code called abort()");var isLeapYear=year=>year%4===0&&(year%100!==0||year%400===0);var MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335];var MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334];var ydayFromDate=date=>{var leap=isLeapYear(date.getFullYear());var monthDaysCumulative=leap?MONTH_DAYS_LEAP_CUMULATIVE:MONTH_DAYS_REGULAR_CUMULATIVE;var yday=monthDaysCumulative[date.getMonth()]+date.getDate()-1;return yday};var INT53_MAX=9007199254740992;var INT53_MIN=-9007199254740992;var bigintToI53Checked=num=>numINT53_MAX?NaN:Number(num);function __localtime_js(time,tmPtr){time=bigintToI53Checked(time);var date=new Date(time*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var yday=ydayFromDate(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst}var __mktime_js=function(tmPtr){var ret=(()=>{var date=new Date(HEAP32[tmPtr+20>>2]+1900,HEAP32[tmPtr+16>>2],HEAP32[tmPtr+12>>2],HEAP32[tmPtr+8>>2],HEAP32[tmPtr+4>>2],HEAP32[tmPtr>>2],0);var dst=HEAP32[tmPtr+32>>2];var guessedOffset=date.getTimezoneOffset();var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dstOffset=Math.min(winterOffset,summerOffset);if(dst<0){HEAP32[tmPtr+32>>2]=Number(summerOffset!=winterOffset&&dstOffset==guessedOffset)}else if(dst>0!=(dstOffset==guessedOffset)){var nonDstOffset=Math.max(winterOffset,summerOffset);var trueOffset=dst>0?dstOffset:nonDstOffset;date.setTime(date.getTime()+(trueOffset-guessedOffset)*6e4)}HEAP32[tmPtr+24>>2]=date.getDay();var yday=ydayFromDate(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getYear();var timeMs=date.getTime();if(isNaN(timeMs)){return-1}return timeMs/1e3})();return BigInt(ret)};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{assert(typeof str==="string",`stringToUTF8Array expects a string (got ${typeof str})`);if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;if(u>1114111)warnOnce("Invalid Unicode code point "+ptrToString(u)+" encountered when serializing a JS string to a UTF-8 string in wasm memory! (Valid unicode code points should be in range 0-0x10FFFF).");heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>{assert(typeof maxBytesToWrite=="number","stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!");return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)};var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var __tzset_js=(timezone,daylight,std_name,dst_name)=>{var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);var extractZone=timezoneOffset=>{var sign=timezoneOffset>=0?"-":"+";var absOffset=Math.abs(timezoneOffset);var hours=String(Math.floor(absOffset/60)).padStart(2,"0");var minutes=String(absOffset%60).padStart(2,"0");return`UTC${sign}${hours}${minutes}`};var winterName=extractZone(winterOffset);var summerName=extractZone(summerOffset);assert(winterName);assert(summerName);assert(lengthBytesUTF8(winterName)<=16,`timezone name truncated to fit in TZNAME_MAX (${winterName})`);assert(lengthBytesUTF8(summerName)<=16,`timezone name truncated to fit in TZNAME_MAX (${summerName})`);if(summerOffsetperformance.now();var _emscripten_date_now=()=>Date.now();var nowIsMonotonic=1;var checkWasiClock=clock_id=>clock_id>=0&&clock_id<=3;function _clock_time_get(clk_id,ignored_precision,ptime){ignored_precision=bigintToI53Checked(ignored_precision);if(!checkWasiClock(clk_id)){return 28}var now;if(clk_id===0){now=_emscripten_date_now()}else if(nowIsMonotonic){now=_emscripten_get_now()}else{return 52}var nsec=Math.round(now*1e3*1e3);HEAP64[ptime>>3]=BigInt(nsec);return 0}var abortOnCannotGrowMemory=requestedSize=>{abort(`Cannot enlarge memory arrays to size ${requestedSize} bytes (OOM). Either (1) compile with -sINITIAL_MEMORY=X with X higher than the current value ${HEAP8.length}, (2) compile with -sALLOW_MEMORY_GROWTH which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with -sABORTING_MALLOC=0`)};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;abortOnCannotGrowMemory(requestedSize)};var ENV={};var getExecutableName=()=>thisProgram||"./this.program";var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.language||"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var _environ_get=(__environ,environ_buf)=>{var bufSize=0;var envp=0;for(var string of getEnvStrings()){var ptr=environ_buf+bufSize;HEAPU32[__environ+envp>>2]=ptr;bufSize+=stringToUTF8(string,ptr,Infinity)+1;envp+=4}return 0};var _environ_sizes_get=(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;for(var string of strings){bufSize+=lengthBytesUTF8(string)+1}HEAPU32[penviron_buf_size>>2]=bufSize;return 0};var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.slice(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.slice(0,-1)}return root+dir},basename:path=>path&&path.match(/([^\/]+|\/)\/*$/)[1],join:(...paths)=>PATH.normalize(paths.join("/")),join2:(l,r)=>PATH.normalize(l+"/"+r)};var initRandomFill=()=>{if(ENVIRONMENT_IS_NODE){var nodeCrypto=require("crypto");return view=>nodeCrypto.randomFillSync(view)}return view=>crypto.getRandomValues(view)};var randomFill=view=>{(randomFill=initRandomFill())(view)};var PATH_FS={resolve:(...args)=>{var resolvedPath="",resolvedAbsolute=false;for(var i=args.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?args[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).slice(1);to=PATH_FS.resolve(to).slice(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var FS_stdin_getChar_buffer=[];var intArrayFromString=(stringy,dontAddNull,length)=>{var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array};var FS_stdin_getChar=()=>{if(!FS_stdin_getChar_buffer.length){var result=null;if(ENVIRONMENT_IS_NODE){var BUFSIZE=256;var buf=Buffer.alloc(BUFSIZE);var bytesRead=0;var fd=process.stdin.fd;try{bytesRead=fs.readSync(fd,buf,0,BUFSIZE)}catch(e){if(e.toString().includes("EOF"))bytesRead=0;else throw e}if(bytesRead>0){result=buf.slice(0,bytesRead).toString("utf-8")}}else if(globalThis.window?.prompt){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else{}if(!result){return null}FS_stdin_getChar_buffer=intArrayFromString(result,true)}return FS_stdin_getChar_buffer.shift()};var TTY={ttys:[],init(){},shutdown(){},register(dev,ops){TTY.ttys[dev]={input:[],output:[],ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close(stream){stream.tty.ops.fsync(stream.tty)},fsync(stream){stream.tty.ops.fsync(stream.tty)},read(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output));tty.output=[]}},ioctl_tcgets(tty){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(tty,optional_actions,data){return 0},ioctl_tiocgwinsz(tty){return[24,80]}},default_tty1_ops:{put_char(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync(tty){if(tty.output?.length>0){err(UTF8ArrayToString(tty.output));tty.output=[]}}}};var mmapAlloc=size=>{abort("internal error: mmapAlloc called but `emscripten_builtin_memalign` native symbol not exported")};var MEMFS={ops_table:null,mount(mount){return MEMFS.createNode(null,"/",16895,0)},createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}MEMFS.ops_table||={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}};var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.atime=node.mtime=node.ctime=Date.now();if(parent){parent.contents[name]=node;parent.atime=parent.mtime=parent.ctime=node.atime}return node},getFileDataAsTypedArray(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.atime);attr.mtime=new Date(node.mtime);attr.ctime=new Date(node.ctime);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr(node,attr){for(const key of["mode","atime","mtime","ctime"]){if(attr[key]!=null){node[key]=attr[key]}}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup(parent,name){throw new FS.ErrnoError(44)},mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename(old_node,new_dir,new_name){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){if(FS.isDir(old_node.mode)){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}FS.hashRemoveNode(new_node)}delete old_node.parent.contents[old_node.name];new_dir.contents[new_name]=old_node;old_node.name=new_name;new_dir.ctime=new_dir.mtime=old_node.parent.ctime=old_node.parent.mtime=Date.now()},unlink(parent,name){delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},rmdir(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},readdir(node){return[".","..",...Object.keys(node.contents)]},symlink(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);assert(size>=0);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{var flagModes={r:0,"r+":2,w:512|64|1,"w+":512|64|2,a:1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags};var FS_getMode=(canRead,canWrite)=>{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>{assert(typeof ptr=="number",`UTF8ToString expects a number (got ${typeof ptr})`);return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):""};var strError=errno=>UTF8ToString(_strerror(errno));var ERRNO_CODES={EPERM:63,ENOENT:44,ESRCH:71,EINTR:27,EIO:29,ENXIO:60,E2BIG:1,ENOEXEC:45,EBADF:8,ECHILD:12,EAGAIN:6,EWOULDBLOCK:6,ENOMEM:48,EACCES:2,EFAULT:21,ENOTBLK:105,EBUSY:10,EEXIST:20,EXDEV:75,ENODEV:43,ENOTDIR:54,EISDIR:31,EINVAL:28,ENFILE:41,EMFILE:33,ENOTTY:59,ETXTBSY:74,EFBIG:22,ENOSPC:51,ESPIPE:70,EROFS:69,EMLINK:34,EPIPE:64,EDOM:18,ERANGE:68,ENOMSG:49,EIDRM:24,ECHRNG:106,EL2NSYNC:156,EL3HLT:107,EL3RST:108,ELNRNG:109,EUNATCH:110,ENOCSI:111,EL2HLT:112,EDEADLK:16,ENOLCK:46,EBADE:113,EBADR:114,EXFULL:115,ENOANO:104,EBADRQC:103,EBADSLT:102,EDEADLOCK:16,EBFONT:101,ENOSTR:100,ENODATA:116,ETIME:117,ENOSR:118,ENONET:119,ENOPKG:120,EREMOTE:121,ENOLINK:47,EADV:122,ESRMNT:123,ECOMM:124,EPROTO:65,EMULTIHOP:36,EDOTDOT:125,EBADMSG:9,ENOTUNIQ:126,EBADFD:127,EREMCHG:128,ELIBACC:129,ELIBBAD:130,ELIBSCN:131,ELIBMAX:132,ELIBEXEC:133,ENOSYS:52,ENOTEMPTY:55,ENAMETOOLONG:37,ELOOP:32,EOPNOTSUPP:138,EPFNOSUPPORT:139,ECONNRESET:15,ENOBUFS:42,EAFNOSUPPORT:5,EPROTOTYPE:67,ENOTSOCK:57,ENOPROTOOPT:50,ESHUTDOWN:140,ECONNREFUSED:14,EADDRINUSE:3,ECONNABORTED:13,ENETUNREACH:40,ENETDOWN:38,ETIMEDOUT:73,EHOSTDOWN:142,EHOSTUNREACH:23,EINPROGRESS:26,EALREADY:7,EDESTADDRREQ:17,EMSGSIZE:35,EPROTONOSUPPORT:66,ESOCKTNOSUPPORT:137,EADDRNOTAVAIL:4,ENETRESET:39,EISCONN:30,ENOTCONN:53,ETOOMANYREFS:141,EUSERS:136,EDQUOT:19,ESTALE:72,ENOTSUP:138,ENOMEDIUM:148,EILSEQ:25,EOVERFLOW:61,ECANCELED:11,ENOTRECOVERABLE:56,EOWNERDEAD:62,ESTRPIPE:135};var asyncLoad=async url=>{var arrayBuffer=await readAsync(url);assert(arrayBuffer,`Loading data file "${url}" failed (no arrayBuffer).`);return new Uint8Array(arrayBuffer)};var FS_createDataFile=(...args)=>FS.createDataFile(...args);var getUniqueRunDependency=id=>{var orig=id;while(1){if(!runDependencyTracking[id])return id;id=orig+Math.random()}};var runDependencies=0;var dependenciesFulfilled=null;var runDependencyTracking={};var runDependencyWatcher=null;var removeRunDependency=id=>{runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);assert(id,"removeRunDependency requires an ID");assert(runDependencyTracking[id]);delete runDependencyTracking[id];if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}};var addRunDependency=id=>{runDependencies++;Module["monitorRunDependencies"]?.(runDependencies);assert(id,"addRunDependency requires an ID");assert(!runDependencyTracking[id]);runDependencyTracking[id]=1;if(runDependencyWatcher===null&&globalThis.setInterval){runDependencyWatcher=setInterval(()=>{if(ABORT){clearInterval(runDependencyWatcher);runDependencyWatcher=null;return}var shown=false;for(var dep in runDependencyTracking){if(!shown){shown=true;err("still waiting on run dependencies:")}err(`dependency: ${dep}`)}if(shown){err("(end of list)")}},1e4);runDependencyWatcher.unref?.()}};var preloadPlugins=[];var FS_handledByPreloadPlugin=async(byteArray,fullname)=>{if(typeof Browser!="undefined")Browser.init();for(var plugin of preloadPlugins){if(plugin["canHandle"](fullname)){assert(plugin["handle"].constructor.name==="AsyncFunction","Filesystem plugin handlers must be async functions (See #24914)");return plugin["handle"](byteArray,fullname)}}return byteArray};var FS_preloadFile=async(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);addRunDependency(dep);try{var byteArray=url;if(typeof url=="string"){byteArray=await asyncLoad(url)}byteArray=await FS_handledByPreloadPlugin(byteArray,fullname);preFinish?.();if(!dontCreateFile){FS_createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}}finally{removeRunDependency(dep)}};var FS_createPreloadedFile=(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{FS_preloadFile(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish).then(onload).catch(onerror)};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,filesystems:null,syncFSRequests:0,readFiles:{},ErrnoError:class extends Error{name="ErrnoError";constructor(errno){super(runtimeInitialized?strError(errno):"");this.errno=errno;for(var key in ERRNO_CODES){if(ERRNO_CODES[key]===errno){this.code=key;break}}}},FSStream:class{shared={};get object(){return this.node}set object(val){this.node=val}get isRead(){return(this.flags&2097155)!==1}get isWrite(){return(this.flags&2097155)!==0}get isAppend(){return this.flags&1024}get flags(){return this.shared.flags}set flags(val){this.shared.flags=val}get position(){return this.shared.position}set position(val){this.shared.position=val}},FSNode:class{node_ops={};stream_ops={};readMode=292|73;writeMode=146;mounted=null;constructor(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.rdev=rdev;this.atime=this.mtime=this.ctime=Date.now()}get read(){return(this.mode&this.readMode)===this.readMode}set read(val){val?this.mode|=this.readMode:this.mode&=~this.readMode}get write(){return(this.mode&this.writeMode)===this.writeMode}set write(val){val?this.mode|=this.writeMode:this.mode&=~this.writeMode}get isFolder(){return FS.isDir(this.mode)}get isDevice(){return FS.isChrdev(this.mode)}},lookupPath(path,opts={}){if(!path){throw new FS.ErrnoError(44)}opts.follow_mount??=true;if(!PATH.isAbs(path)){path=FS.cwd()+"/"+path}linkloop:for(var nlinks=0;nlinks<40;nlinks++){var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i>>0)%FS.nameTable.length},hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode(parent,name,mode,rdev){assert(typeof parent=="object");var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode(node){FS.hashRemoveNode(node)},isRoot(node){return node===node.parent},isMountpoint(node){return!!node.mounted},isFile(mode){return(mode&61440)===32768},isDir(mode){return(mode&61440)===16384},isLink(mode){return(mode&61440)===40960},isChrdev(mode){return(mode&61440)===8192},isBlkdev(mode){return(mode&61440)===24576},isFIFO(mode){return(mode&61440)===4096},isSocket(mode){return(mode&49152)===49152},flagsToPermissionString(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions(node,perms){if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup(dir){if(!FS.isDir(dir.mode))return 54;var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate(dir,name){if(!FS.isDir(dir.mode)){return 54}try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&(512|64)){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},checkOpExists(op,err){if(!op){throw new FS.ErrnoError(err)}return op},MAX_OPEN_FDS:4096,nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStreamChecked(fd){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}return stream},getStream:fd=>FS.streams[fd],createStream(stream,fd=-1){assert(fd>=-1);stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream(fd){FS.streams[fd]=null},dupStream(origStream,fd=-1){var stream=FS.createStream(origStream,fd);stream.stream_ops?.dup?.(stream);return stream},doSetAttr(stream,node,attr){var setattr=stream?.stream_ops.setattr;var arg=setattr?stream:node;setattr??=node.node_ops.setattr;FS.checkOpExists(setattr,63);setattr(arg,attr)},chrdev_stream_ops:{open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;stream.stream_ops.open?.(stream)},llseek(){throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push(...m.mounts)}return mounts},syncfs(populate,callback){if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){assert(FS.syncFSRequests>0);FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount(type,opts,mountpoint){if(typeof type=="string"){throw type}var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type,opts,mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);assert(idx!==-1);node.mount.mounts.splice(idx,1)},lookup(parent,name){return parent.node_ops.lookup(parent,name)},mknod(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name){throw new FS.ErrnoError(28)}if(name==="."||name===".."){throw new FS.ErrnoError(20)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},statfs(path){return FS.statfsNode(FS.lookupPath(path,{follow:true}).node)},statfsStream(stream){return FS.statfsNode(stream.node)},statfsNode(node){var rtn={bsize:4096,frsize:4096,blocks:1e6,bfree:5e5,bavail:5e5,files:FS.nextInode,ffree:FS.nextInode-1,fsid:42,flags:2,namelen:255};if(node.node_ops.statfs){Object.assign(rtn,node.node_ops.statfs(node.mount.opts.root))}return rtn},create(path,mode=438){mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir(path,mode=511){mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree(path,mode){var dirs=path.split("/");var d="";for(var dir of dirs){if(!dir)continue;if(d||PATH.isAbs(path))d+="/";d+=dir;try{FS.mkdir(d,mode)}catch(e){if(e.errno!=20)throw e}}},mkdev(path,mode,dev){if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink(oldpath,newpath){if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename(old_path,new_path){var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name);old_node.parent=new_dir}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir(path){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var readdir=FS.checkOpExists(node.node_ops.readdir,54);return readdir(node)},unlink(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink(path){var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return link.node_ops.readlink(link)},stat(path,dontFollow){var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;var getattr=FS.checkOpExists(node.node_ops.getattr,63);return getattr(node)},fstat(fd){var stream=FS.getStreamChecked(fd);var node=stream.node;var getattr=stream.stream_ops.getattr;var arg=getattr?stream:node;getattr??=node.node_ops.getattr;FS.checkOpExists(getattr,63);return getattr(arg)},lstat(path){return FS.stat(path,true)},doChmod(stream,node,mode,dontFollow){FS.doSetAttr(stream,node,{mode:mode&4095|node.mode&~4095,ctime:Date.now(),dontFollow})},chmod(path,mode,dontFollow){var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}FS.doChmod(null,node,mode,dontFollow)},lchmod(path,mode){FS.chmod(path,mode,true)},fchmod(fd,mode){var stream=FS.getStreamChecked(fd);FS.doChmod(stream,stream.node,mode,false)},doChown(stream,node,dontFollow){FS.doSetAttr(stream,node,{timestamp:Date.now(),dontFollow})},chown(path,uid,gid,dontFollow){var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}FS.doChown(null,node,dontFollow)},lchown(path,uid,gid){FS.chown(path,uid,gid,true)},fchown(fd,uid,gid){var stream=FS.getStreamChecked(fd);FS.doChown(stream,stream.node,false)},doTruncate(stream,node,len){if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}FS.doSetAttr(stream,node,{size:len,timestamp:Date.now()})},truncate(path,len){if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}FS.doTruncate(null,node,len)},ftruncate(fd,len){var stream=FS.getStreamChecked(fd);if(len<0||(stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.doTruncate(stream,stream.node,len)},utime(path,atime,mtime){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var setattr=FS.checkOpExists(node.node_ops.setattr,63);setattr(node,{atime,mtime})},open(path,flags,mode=438){if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags=="string"?FS_modeStringToFlags(flags):flags;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;var isDirPath;if(typeof path=="object"){node=path}else{isDirPath=path.endsWith("/");var lookup=FS.lookupPath(path,{follow:!(flags&131072),noent_okay:true});node=lookup.node;path=lookup.path}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else if(isDirPath){throw new FS.ErrnoError(31)}else{node=FS.mknod(path,mode|511,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node,path:FS.getPath(node),flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(created){FS.chmod(node,mode&511)}if(Module["logReadFiles"]&&!(flags&1)){if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close(stream){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed(stream){return stream.fd===null},llseek(stream,offset,whence){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read(stream,buffer,offset,length,position){assert(offset>=0);if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write(stream,buffer,offset,length,position,canOwn){assert(offset>=0);if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},mmap(stream,length,position,prot,flags){if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}if(!length){throw new FS.ErrnoError(28)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync(stream,buffer,offset,length,mmapFlags){assert(offset>=0);if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},ioctl(stream,cmd,arg){if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile(path,opts={}){opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){abort(`Invalid encoding type "${opts.encoding}"`)}var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){buf=UTF8ArrayToString(buf)}FS.close(stream);return buf},writeFile(path,data,opts={}){opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){data=new Uint8Array(intArrayFromString(data,true))}if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{abort("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length,llseek:()=>0});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomFill(randomBuffer);randomLeft=randomBuffer.byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount(){var node=FS.createNode(proc_self,"fd",16895,73);node.stream_ops={llseek:MEMFS.stream_ops.llseek};node.node_ops={lookup(parent,name){var fd=+name;var stream=FS.getStreamChecked(fd);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path},id:fd+1};ret.parent=ret;return ret},readdir(){return Array.from(FS.streams.entries()).filter(([k,v])=>v).map(([k,v])=>k.toString())}};return node}},{},"/proc/self/fd")},createStandardStreams(input,output,error){if(input){FS.createDevice("/dev","stdin",input)}else{FS.symlink("/dev/tty","/dev/stdin")}if(output){FS.createDevice("/dev","stdout",null,output)}else{FS.symlink("/dev/tty","/dev/stdout")}if(error){FS.createDevice("/dev","stderr",null,error)}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1);assert(stdin.fd===0,`invalid handle for stdin (${stdin.fd})`);assert(stdout.fd===1,`invalid handle for stdout (${stdout.fd})`);assert(stderr.fd===2,`invalid handle for stderr (${stderr.fd})`)},staticInit(){FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={MEMFS}},init(input,output,error){assert(!FS.initialized,"FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)");FS.initialized=true;input??=Module["stdin"];output??=Module["stdout"];error??=Module["stderr"];FS.createStandardStreams(input,output,error)},quit(){FS.initialized=false;_fflush(0);for(var stream of FS.streams){if(stream){FS.close(stream)}}},findObject(path,dontResolveLastLink){var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath(path,dontResolveLastLink){try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath(parent,path,canRead,canWrite){parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){if(e.errno!=20)throw e}parent=current}return current},createFile(parent,name,properties,canRead,canWrite){var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile(parent,name,data,canRead,canWrite,canOwn){var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS_getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]}setDataGetter(getter){this.getter=getter}cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)abort("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)abort("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")abort("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true}get length(){if(!this.lengthKnown){this.cacheLength()}return this._length}get chunkSize(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}if(globalThis.XMLHttpRequest){if(!ENVIRONMENT_IS_WORKER)abort("Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc");var lazyArray=new LazyUint8Array;var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=(...args)=>{FS.forceLoadFile(node);return fn(...args)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);assert(size>=0);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr,allocated:true}};node.stream_ops=stream_ops;return node},absolutePath(){abort("FS.absolutePath has been removed; use PATH_FS.resolve instead")},createFolder(){abort("FS.createFolder has been removed; use FS.mkdir instead")},createLink(){abort("FS.createLink has been removed; use FS.symlink instead")},joinPath(){abort("FS.joinPath has been removed; use PATH.join instead")},mmapAlloc(){abort("FS.mmapAlloc has been replaced by the top level function mmapAlloc")},standardizePath(){abort("FS.standardizePath has been removed; use PATH.normalize instead")}};var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return dir+"/"+path},writeStat(buf,stat){HEAPU32[buf>>2]=stat.dev;HEAPU32[buf+4>>2]=stat.mode;HEAPU32[buf+8>>2]=stat.nlink;HEAPU32[buf+12>>2]=stat.uid;HEAPU32[buf+16>>2]=stat.gid;HEAPU32[buf+20>>2]=stat.rdev;HEAP64[buf+24>>3]=BigInt(stat.size);HEAP32[buf+32>>2]=4096;HEAP32[buf+36>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();HEAP64[buf+40>>3]=BigInt(Math.floor(atime/1e3));HEAPU32[buf+48>>2]=atime%1e3*1e3*1e3;HEAP64[buf+56>>3]=BigInt(Math.floor(mtime/1e3));HEAPU32[buf+64>>2]=mtime%1e3*1e3*1e3;HEAP64[buf+72>>3]=BigInt(Math.floor(ctime/1e3));HEAPU32[buf+80>>2]=ctime%1e3*1e3*1e3;HEAP64[buf+88>>3]=BigInt(stat.ino);return 0},writeStatFs(buf,stats){HEAPU32[buf+4>>2]=stats.bsize;HEAPU32[buf+60>>2]=stats.bsize;HEAP64[buf+8>>3]=BigInt(stats.blocks);HEAP64[buf+16>>3]=BigInt(stats.bfree);HEAP64[buf+24>>3]=BigInt(stats.bavail);HEAP64[buf+32>>3]=BigInt(stats.files);HEAP64[buf+40>>3]=BigInt(stats.ffree);HEAPU32[buf+48>>2]=stats.fsid;HEAPU32[buf+64>>2]=stats.flags;HEAPU32[buf+56>>2]=stats.namelen},doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},getStreamFromFD(fd){var stream=FS.getStreamChecked(fd);return stream},varargs:undefined,getStr(ptr){var ret=UTF8ToString(ptr);return ret}};function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doReadv=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_seek(fd,offset,whence,newOffset){offset=bigintToI53Checked(offset);try{if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);HEAP64[newOffset>>3]=BigInt(stream.position);if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doWritev=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var runAndAbortIfError=func=>{try{return func()}catch(e){abort(e)}};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}checkStackCookie();if(e instanceof WebAssembly.RuntimeError){if(_emscripten_stack_get_current()<=0){err("Stack overflow detected. You can try increasing -sSTACK_SIZE (currently set to 65536)")}}quit_(1,e)};var runtimeKeepaliveCounter=0;var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;checkUnflushedContent();if(keepRuntimeAlive()&&!implicit){var msg=`program exited (with status: ${status}), but keepRuntimeAlive() is set (counter=${runtimeKeepaliveCounter}) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)`;readyPromiseReject?.(msg);err(msg)}_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){err("user callback triggered after runtime exited or application aborted. Ignoring.");return}try{func();maybeExit()}catch(e){handleException(e)}};var createNamedFunction=(name,func)=>Object.defineProperty(func,"name",{value:name});var runtimeKeepalivePush=()=>{runtimeKeepaliveCounter+=1};var runtimeKeepalivePop=()=>{assert(runtimeKeepaliveCounter>0);runtimeKeepaliveCounter-=1};var Asyncify={instrumentWasmImports(imports){var importPattern=/^(write_ep1|write_ep2|read_ep1|read_ep2|write_log_js|invoke_.*|__asyncjs__.*)$/;for(let[x,original]of Object.entries(imports)){if(typeof original=="function"){let isAsyncifyImport=original.isAsync||importPattern.test(x);imports[x]=(...args)=>{var originalAsyncifyState=Asyncify.state;try{return original(...args)}finally{var changedToDisabled=originalAsyncifyState===Asyncify.State.Normal&&Asyncify.state===Asyncify.State.Disabled;var ignoredInvoke=x.startsWith("invoke_")&&true;if(Asyncify.state!==originalAsyncifyState&&!isAsyncifyImport&&!changedToDisabled&&!ignoredInvoke){abort(`import ${x} was not in ASYNCIFY_IMPORTS, but changed the state`)}}}}}},instrumentFunction(original){var wrapper=(...args)=>{Asyncify.exportCallStack.push(original);try{return original(...args)}finally{if(!ABORT){var top=Asyncify.exportCallStack.pop();assert(top===original);Asyncify.maybeStopUnwind()}}};Asyncify.funcWrappers.set(original,wrapper);wrapper=createNamedFunction(`__asyncify_wrapper_${original.name}`,wrapper);return wrapper},instrumentWasmExports(exports){var ret={};for(let[x,original]of Object.entries(exports)){if(typeof original=="function"){var wrapper=Asyncify.instrumentFunction(original);ret[x]=wrapper}else{ret[x]=original}}return ret},State:{Normal:0,Unwinding:1,Rewinding:2,Disabled:3},state:0,StackSize:4096,currData:null,handleSleepReturnValue:0,exportCallStack:[],callstackFuncToId:new Map,callStackIdToFunc:new Map,funcWrappers:new Map,callStackId:0,asyncPromiseHandlers:null,sleepCallbacks:[],getCallStackId(func){assert(func);if(!Asyncify.callstackFuncToId.has(func)){var id=Asyncify.callStackId++;Asyncify.callstackFuncToId.set(func,id);Asyncify.callStackIdToFunc.set(id,func)}return Asyncify.callstackFuncToId.get(func)},maybeStopUnwind(){if(Asyncify.currData&&Asyncify.state===Asyncify.State.Unwinding&&Asyncify.exportCallStack.length===0){Asyncify.state=Asyncify.State.Normal;runAndAbortIfError(_asyncify_stop_unwind);if(typeof Fibers!="undefined"){Fibers.trampoline()}}},whenDone(){assert(Asyncify.currData,"Tried to wait for an async operation when none is in progress.");assert(!Asyncify.asyncPromiseHandlers,"Cannot have multiple async operations in flight at once");return new Promise((resolve,reject)=>{Asyncify.asyncPromiseHandlers={resolve,reject}})},allocateData(){var ptr=_malloc(12+Asyncify.StackSize);Asyncify.setDataHeader(ptr,ptr+12,Asyncify.StackSize);Asyncify.setDataRewindFunc(ptr);return ptr},setDataHeader(ptr,stack,stackSize){HEAPU32[ptr>>2]=stack;HEAPU32[ptr+4>>2]=stack+stackSize},setDataRewindFunc(ptr){var bottomOfCallStack=Asyncify.exportCallStack[0];assert(bottomOfCallStack,"exportCallStack is empty");var rewindId=Asyncify.getCallStackId(bottomOfCallStack);HEAP32[ptr+8>>2]=rewindId},getDataRewindFunc(ptr){var id=HEAP32[ptr+8>>2];var func=Asyncify.callStackIdToFunc.get(id);assert(func,`id ${id} not found in callStackIdToFunc`);return func},doRewind(ptr){var original=Asyncify.getDataRewindFunc(ptr);var func=Asyncify.funcWrappers.get(original);assert(original);assert(func);return func()},handleSleep(startAsync){assert(Asyncify.state!==Asyncify.State.Disabled,"Asyncify cannot be done during or after the runtime exits");if(ABORT)return;if(Asyncify.state===Asyncify.State.Normal){var reachedCallback=false;var reachedAfterCallback=false;startAsync((handleSleepReturnValue=0)=>{assert(!handleSleepReturnValue||typeof handleSleepReturnValue=="number"||typeof handleSleepReturnValue=="boolean");if(ABORT)return;Asyncify.handleSleepReturnValue=handleSleepReturnValue;reachedCallback=true;if(!reachedAfterCallback){return}assert(!Asyncify.exportCallStack.length,"Waking up (starting to rewind) must be done from JS, without compiled code on the stack.");Asyncify.state=Asyncify.State.Rewinding;runAndAbortIfError(()=>_asyncify_start_rewind(Asyncify.currData));if(typeof MainLoop!="undefined"&&MainLoop.func){MainLoop.resume()}var asyncWasmReturnValue,isError=false;try{asyncWasmReturnValue=Asyncify.doRewind(Asyncify.currData)}catch(err){asyncWasmReturnValue=err;isError=true}var handled=false;if(!Asyncify.currData){var asyncPromiseHandlers=Asyncify.asyncPromiseHandlers;if(asyncPromiseHandlers){Asyncify.asyncPromiseHandlers=null;(isError?asyncPromiseHandlers.reject:asyncPromiseHandlers.resolve)(asyncWasmReturnValue);handled=true}}if(isError&&!handled){throw asyncWasmReturnValue}});reachedAfterCallback=true;if(!reachedCallback){Asyncify.state=Asyncify.State.Unwinding;Asyncify.currData=Asyncify.allocateData();if(typeof MainLoop!="undefined"&&MainLoop.func){MainLoop.pause()}runAndAbortIfError(()=>_asyncify_start_unwind(Asyncify.currData))}}else if(Asyncify.state===Asyncify.State.Rewinding){Asyncify.state=Asyncify.State.Normal;runAndAbortIfError(_asyncify_stop_rewind);_free(Asyncify.currData);Asyncify.currData=null;Asyncify.sleepCallbacks.forEach(callUserCallback)}else{abort(`invalid state: ${Asyncify.state}`)}return Asyncify.handleSleepReturnValue},handleAsync:startAsync=>Asyncify.handleSleep(wakeUp=>{startAsync().then(wakeUp)})};var getCFunc=ident=>{var func=Module["_"+ident];assert(func,"Cannot call unknown function "+ident+", make sure it is exported");return func};var writeArrayToMemory=(array,buffer)=>{assert(array.length>=0,"writeArrayToMemory array must have a length (should be an array or typed array)");HEAP8.set(array,buffer)};var stackAlloc=sz=>__emscripten_stack_alloc(sz);var stringToUTF8OnStack=str=>{var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret};var ccall=(ident,returnType,argTypes,args,opts)=>{var toC={string:str=>{var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=stringToUTF8OnStack(str)}return ret},array:arr=>{var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;assert(returnType!=="array",'Return type should not be "array".');if(args){for(var i=0;i(...args)=>ccall(ident,returnType,argTypes,args,opts);var stringToAscii=(str,buffer)=>{for(var i=0;i{var str="";while(1){var ch=HEAPU8[ptr++];if(!ch)return str;str+=String.fromCharCode(ch)}};FS.createPreloadedFile=FS_createPreloadedFile;FS.preloadFile=FS_preloadFile;FS.staticInit();{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["preloadPlugins"])preloadPlugins=Module["preloadPlugins"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];checkIncomingModuleAPI();if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];assert(typeof Module["memoryInitializerPrefixURL"]=="undefined","Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead");assert(typeof Module["pthreadMainPrefixURL"]=="undefined","Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead");assert(typeof Module["cdInitializerPrefixURL"]=="undefined","Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead");assert(typeof Module["filePackagePrefixURL"]=="undefined","Module.filePackagePrefixURL option was removed, use Module.locateFile instead");assert(typeof Module["read"]=="undefined","Module.read option was removed");assert(typeof Module["readAsync"]=="undefined","Module.readAsync option was removed (modify readAsync in JS)");assert(typeof Module["readBinary"]=="undefined","Module.readBinary option was removed (modify readBinary in JS)");assert(typeof Module["setWindowTitle"]=="undefined","Module.setWindowTitle option was removed (modify emscripten_set_window_title in JS)");assert(typeof Module["TOTAL_MEMORY"]=="undefined","Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY");assert(typeof Module["ENVIRONMENT"]=="undefined","Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)");assert(typeof Module["STACK_SIZE"]=="undefined","STACK_SIZE can no longer be set at runtime. Use -sSTACK_SIZE at link time");assert(typeof Module["wasmMemory"]=="undefined","Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally");assert(typeof Module["INITIAL_MEMORY"]=="undefined","Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically");if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}consumedModuleProp("preInit")}Module["ccall"]=ccall;Module["cwrap"]=cwrap;Module["AsciiToString"]=AsciiToString;Module["stringToAscii"]=stringToAscii;var missingLibrarySymbols=["writeI53ToI64","writeI53ToI64Clamped","writeI53ToI64Signaling","writeI53ToU64Clamped","writeI53ToU64Signaling","readI53FromI64","readI53FromU64","convertI32PairToI53","convertI32PairToI53Checked","convertU32PairToI53","getTempRet0","setTempRet0","zeroMemory","getHeapMax","growMemory","withStackSave","inetPton4","inetNtop4","inetPton6","inetNtop6","readSockaddr","writeSockaddr","readEmAsmArgs","jstoi_q","autoResumeAudioContext","getDynCaller","asmjsMangle","alignMemory","HandleAllocator","addOnInit","addOnPostCtor","addOnPreMain","addOnExit","STACK_SIZE","STACK_ALIGN","POINTER_SIZE","ASSERTIONS","convertJsFunctionToWasm","getEmptyTableSlot","updateTableMap","getFunctionAddress","addFunction","removeFunction","intArrayToString","UTF16ToString","stringToUTF16","lengthBytesUTF16","UTF32ToString","stringToUTF32","lengthBytesUTF32","stringToNewUTF8","registerKeyEventCallback","maybeCStringToJsString","findEventTarget","getBoundingClientRect","fillMouseEventData","registerMouseEventCallback","registerWheelEventCallback","registerUiEventCallback","registerFocusEventCallback","fillDeviceOrientationEventData","registerDeviceOrientationEventCallback","fillDeviceMotionEventData","registerDeviceMotionEventCallback","screenOrientation","fillOrientationChangeEventData","registerOrientationChangeEventCallback","fillFullscreenChangeEventData","registerFullscreenChangeEventCallback","JSEvents_requestFullscreen","JSEvents_resizeCanvasForFullscreen","registerRestoreOldStyle","hideEverythingExceptGivenElement","restoreHiddenElements","setLetterbox","softFullscreenResizeWebGLRenderTarget","doRequestFullscreen","fillPointerlockChangeEventData","registerPointerlockChangeEventCallback","registerPointerlockErrorEventCallback","requestPointerLock","fillVisibilityChangeEventData","registerVisibilityChangeEventCallback","registerTouchEventCallback","fillGamepadEventData","registerGamepadEventCallback","registerBeforeUnloadEventCallback","fillBatteryEventData","registerBatteryEventCallback","setCanvasElementSize","getCanvasElementSize","jsStackTrace","getCallstack","convertPCtoSourceLocation","wasiRightsToMuslOFlags","wasiOFlagsToMuslOFlags","safeSetTimeout","setImmediateWrapped","safeRequestAnimationFrame","clearImmediateWrapped","registerPostMainLoop","registerPreMainLoop","getPromise","makePromise","idsToPromises","makePromiseCallback","ExceptionInfo","findMatchingCatch","Browser_asyncPrepareDataCounter","arraySum","addDays","getSocketFromFD","getSocketAddress","FS_mkdirTree","_setNetworkCallback","heapObjectForWebGLType","toTypedArrayIndex","webgl_enable_ANGLE_instanced_arrays","webgl_enable_OES_vertex_array_object","webgl_enable_WEBGL_draw_buffers","webgl_enable_WEBGL_multi_draw","webgl_enable_EXT_polygon_offset_clamp","webgl_enable_EXT_clip_control","webgl_enable_WEBGL_polygon_mode","emscriptenWebGLGet","computeUnpackAlignedImageSize","colorChannelsInGlTextureFormat","emscriptenWebGLGetTexPixelData","emscriptenWebGLGetUniform","webglGetUniformLocation","webglPrepareUniformLocationsBeforeFirstUse","webglGetLeftBracePos","emscriptenWebGLGetVertexAttrib","__glGetActiveAttribOrUniform","writeGLArray","registerWebGlEventCallback","ALLOC_NORMAL","ALLOC_STACK","allocate","writeStringToMemory","writeAsciiToMemory","allocateUTF8","allocateUTF8OnStack","demangle","stackTrace","getNativeTypeSize"];missingLibrarySymbols.forEach(missingLibrarySymbol);var unexportedSymbols=["run","out","err","callMain","abort","wasmExports","HEAPF64","HEAP64","HEAPU64","writeStackCookie","checkStackCookie","INT53_MAX","INT53_MIN","bigintToI53Checked","stackSave","stackRestore","stackAlloc","createNamedFunction","ptrToString","exitJS","abortOnCannotGrowMemory","ENV","ERRNO_CODES","strError","DNS","Protocols","Sockets","timers","warnOnce","readEmAsmArgsArray","getExecutableName","dynCallLegacy","dynCall","handleException","keepRuntimeAlive","runtimeKeepalivePush","runtimeKeepalivePop","callUserCallback","maybeExit","asyncLoad","mmapAlloc","wasmTable","wasmMemory","getUniqueRunDependency","noExitRuntime","addRunDependency","removeRunDependency","addOnPreRun","addOnPostRun","freeTableIndexes","functionsInTableMap","setValue","getValue","PATH","PATH_FS","UTF8Decoder","UTF8ArrayToString","UTF8ToString","stringToUTF8Array","stringToUTF8","lengthBytesUTF8","intArrayFromString","UTF16Decoder","stringToUTF8OnStack","writeArrayToMemory","JSEvents","specialHTMLTargets","findCanvasEventTarget","currentFullscreenStrategy","restoreOldWindowedStyle","UNWIND_CACHE","ExitStatus","getEnvStrings","checkWasiClock","doReadv","doWritev","initRandomFill","randomFill","emSetImmediate","emClearImmediate_deps","emClearImmediate","promiseMap","uncaughtExceptionCount","exceptionLast","exceptionCaught","Browser","requestFullscreen","requestFullScreen","setCanvasSize","getUserMedia","createContext","getPreloadedImageData__data","wget","MONTH_DAYS_REGULAR","MONTH_DAYS_LEAP","MONTH_DAYS_REGULAR_CUMULATIVE","MONTH_DAYS_LEAP_CUMULATIVE","isLeapYear","ydayFromDate","SYSCALLS","preloadPlugins","FS_createPreloadedFile","FS_preloadFile","FS_modeStringToFlags","FS_getMode","FS_stdin_getChar_buffer","FS_stdin_getChar","FS_unlink","FS_createPath","FS_createDevice","FS_readFile","FS","FS_root","FS_mounts","FS_devices","FS_streams","FS_nextInode","FS_nameTable","FS_currentPath","FS_initialized","FS_ignorePermissions","FS_filesystems","FS_syncFSRequests","FS_readFiles","FS_lookupPath","FS_getPath","FS_hashName","FS_hashAddNode","FS_hashRemoveNode","FS_lookupNode","FS_createNode","FS_destroyNode","FS_isRoot","FS_isMountpoint","FS_isFile","FS_isDir","FS_isLink","FS_isChrdev","FS_isBlkdev","FS_isFIFO","FS_isSocket","FS_flagsToPermissionString","FS_nodePermissions","FS_mayLookup","FS_mayCreate","FS_mayDelete","FS_mayOpen","FS_checkOpExists","FS_nextfd","FS_getStreamChecked","FS_getStream","FS_createStream","FS_closeStream","FS_dupStream","FS_doSetAttr","FS_chrdev_stream_ops","FS_major","FS_minor","FS_makedev","FS_registerDevice","FS_getDevice","FS_getMounts","FS_syncfs","FS_mount","FS_unmount","FS_lookup","FS_mknod","FS_statfs","FS_statfsStream","FS_statfsNode","FS_create","FS_mkdir","FS_mkdev","FS_symlink","FS_rename","FS_rmdir","FS_readdir","FS_readlink","FS_stat","FS_fstat","FS_lstat","FS_doChmod","FS_chmod","FS_lchmod","FS_fchmod","FS_doChown","FS_chown","FS_lchown","FS_fchown","FS_doTruncate","FS_truncate","FS_ftruncate","FS_utime","FS_open","FS_close","FS_isClosed","FS_llseek","FS_read","FS_write","FS_mmap","FS_msync","FS_ioctl","FS_writeFile","FS_cwd","FS_chdir","FS_createDefaultDirectories","FS_createDefaultDevices","FS_createSpecialDirectories","FS_createStandardStreams","FS_staticInit","FS_init","FS_quit","FS_findObject","FS_analyzePath","FS_createFile","FS_createDataFile","FS_forceLoadFile","FS_createLazyFile","FS_absolutePath","FS_createFolder","FS_createLink","FS_joinPath","FS_mmapAlloc","FS_standardizePath","MEMFS","TTY","PIPEFS","SOCKFS","tempFixedLengthArray","miniTempWebGLFloatBuffers","miniTempWebGLIntBuffers","GL","AL","GLUT","EGL","GLEW","IDBStore","runAndAbortIfError","Asyncify","Fibers","SDL","SDL_gfx","print","printErr","jstoi_s"];unexportedSymbols.forEach(unexportedRuntimeSymbol);Module["ccall"]=ccall;Module["stringToAscii"]=stringToAscii;Module["AsciiToString"]=AsciiToString;function checkIncomingModuleAPI(){ignoredModuleProp("fetchSettings")}function __asyncjs__write_ep1(fd,data,len){return Asyncify.handleAsync(async()=>await write_ep1(fd,data,len))}function __asyncjs__read_ep1(fd,data,len){return Asyncify.handleAsync(async()=>await read_ep1(fd,data,len))}function __asyncjs__write_ep2(fd,data,len){return Asyncify.handleAsync(async()=>await write_ep2(fd,data,len))}function __asyncjs__read_ep2(fd,data,len){return Asyncify.handleAsync(async()=>await read_ep2(fd,data,len))}function __asyncjs__write_log(fd,sevirity,str){return Asyncify.handleAsync(async()=>write_log_js(fd,sevirity,str))}var _init_lib=Module["_init_lib"]=makeInvalidEarlyAccess("_init_lib");var _close_device=Module["_close_device"]=makeInvalidEarlyAccess("_close_device");var _send_command=Module["_send_command"]=makeInvalidEarlyAccess("_send_command");var _send_debug_command=Module["_send_debug_command"]=makeInvalidEarlyAccess("_send_debug_command");var _fflush=makeInvalidEarlyAccess("_fflush");var _strerror=makeInvalidEarlyAccess("_strerror");var _malloc=Module["_malloc"]=makeInvalidEarlyAccess("_malloc");var _free=Module["_free"]=makeInvalidEarlyAccess("_free");var _emscripten_stack_init=makeInvalidEarlyAccess("_emscripten_stack_init");var _emscripten_stack_get_free=makeInvalidEarlyAccess("_emscripten_stack_get_free");var _emscripten_stack_get_base=makeInvalidEarlyAccess("_emscripten_stack_get_base");var _emscripten_stack_get_end=makeInvalidEarlyAccess("_emscripten_stack_get_end");var __emscripten_stack_restore=makeInvalidEarlyAccess("__emscripten_stack_restore");var __emscripten_stack_alloc=makeInvalidEarlyAccess("__emscripten_stack_alloc");var _emscripten_stack_get_current=makeInvalidEarlyAccess("_emscripten_stack_get_current");var dynCall_ii=makeInvalidEarlyAccess("dynCall_ii");var dynCall_iiii=makeInvalidEarlyAccess("dynCall_iiii");var dynCall_jiji=makeInvalidEarlyAccess("dynCall_jiji");var dynCall_iidiiii=makeInvalidEarlyAccess("dynCall_iidiiii");var dynCall_vii=makeInvalidEarlyAccess("dynCall_vii");var dynCall_iii=makeInvalidEarlyAccess("dynCall_iii");var dynCall_iij=makeInvalidEarlyAccess("dynCall_iij");var dynCall_viiii=makeInvalidEarlyAccess("dynCall_viiii");var dynCall_iiiii=makeInvalidEarlyAccess("dynCall_iiiii");var dynCall_iiij=makeInvalidEarlyAccess("dynCall_iiij");var dynCall_iiiijii=makeInvalidEarlyAccess("dynCall_iiiijii");var dynCall_iiiiiiiii=makeInvalidEarlyAccess("dynCall_iiiiiiiii");var dynCall_vi=makeInvalidEarlyAccess("dynCall_vi");var dynCall_viiiii=makeInvalidEarlyAccess("dynCall_viiiii");var dynCall_iiji=makeInvalidEarlyAccess("dynCall_iiji");var dynCall_iijii=makeInvalidEarlyAccess("dynCall_iijii");var dynCall_iiiiiii=makeInvalidEarlyAccess("dynCall_iiiiiii");var dynCall_viiiiiii=makeInvalidEarlyAccess("dynCall_viiiiiii");var dynCall_iiiiii=makeInvalidEarlyAccess("dynCall_iiiiii");var dynCall_iiiiiiii=makeInvalidEarlyAccess("dynCall_iiiiiiii");var dynCall_iijiiiiii=makeInvalidEarlyAccess("dynCall_iijiiiiii");var _asyncify_start_unwind=makeInvalidEarlyAccess("_asyncify_start_unwind");var _asyncify_stop_unwind=makeInvalidEarlyAccess("_asyncify_stop_unwind");var _asyncify_start_rewind=makeInvalidEarlyAccess("_asyncify_start_rewind");var _asyncify_stop_rewind=makeInvalidEarlyAccess("_asyncify_stop_rewind");var memory=makeInvalidEarlyAccess("memory");var __indirect_function_table=makeInvalidEarlyAccess("__indirect_function_table");var wasmMemory=makeInvalidEarlyAccess("wasmMemory");function assignWasmExports(wasmExports){assert(typeof wasmExports["init_lib"]!="undefined","missing Wasm export: init_lib");_init_lib=Module["_init_lib"]=createExportWrapper("init_lib",3);assert(typeof wasmExports["close_device"]!="undefined","missing Wasm export: close_device");_close_device=Module["_close_device"]=createExportWrapper("close_device",1);assert(typeof wasmExports["send_command"]!="undefined","missing Wasm export: send_command");_send_command=Module["_send_command"]=createExportWrapper("send_command",5);assert(typeof wasmExports["send_debug_command"]!="undefined","missing Wasm export: send_debug_command");_send_debug_command=Module["_send_debug_command"]=createExportWrapper("send_debug_command",5);assert(typeof wasmExports["fflush"]!="undefined","missing Wasm export: fflush");_fflush=createExportWrapper("fflush",1);assert(typeof wasmExports["strerror"]!="undefined","missing Wasm export: strerror");_strerror=createExportWrapper("strerror",1);assert(typeof wasmExports["malloc"]!="undefined","missing Wasm export: malloc");_malloc=Module["_malloc"]=createExportWrapper("malloc",1);assert(typeof wasmExports["free"]!="undefined","missing Wasm export: free");_free=Module["_free"]=createExportWrapper("free",1);assert(typeof wasmExports["emscripten_stack_init"]!="undefined","missing Wasm export: emscripten_stack_init");_emscripten_stack_init=wasmExports["emscripten_stack_init"];assert(typeof wasmExports["emscripten_stack_get_free"]!="undefined","missing Wasm export: emscripten_stack_get_free");_emscripten_stack_get_free=wasmExports["emscripten_stack_get_free"];assert(typeof wasmExports["emscripten_stack_get_base"]!="undefined","missing Wasm export: emscripten_stack_get_base");_emscripten_stack_get_base=wasmExports["emscripten_stack_get_base"];assert(typeof wasmExports["emscripten_stack_get_end"]!="undefined","missing Wasm export: emscripten_stack_get_end");_emscripten_stack_get_end=wasmExports["emscripten_stack_get_end"];assert(typeof wasmExports["_emscripten_stack_restore"]!="undefined","missing Wasm export: _emscripten_stack_restore");__emscripten_stack_restore=wasmExports["_emscripten_stack_restore"];assert(typeof wasmExports["_emscripten_stack_alloc"]!="undefined","missing Wasm export: _emscripten_stack_alloc");__emscripten_stack_alloc=wasmExports["_emscripten_stack_alloc"];assert(typeof wasmExports["emscripten_stack_get_current"]!="undefined","missing Wasm export: emscripten_stack_get_current");_emscripten_stack_get_current=wasmExports["emscripten_stack_get_current"];assert(typeof wasmExports["dynCall_ii"]!="undefined","missing Wasm export: dynCall_ii");dynCall_ii=dynCalls["ii"]=createExportWrapper("dynCall_ii",2);assert(typeof wasmExports["dynCall_iiii"]!="undefined","missing Wasm export: dynCall_iiii");dynCall_iiii=dynCalls["iiii"]=createExportWrapper("dynCall_iiii",4);assert(typeof wasmExports["dynCall_jiji"]!="undefined","missing Wasm export: dynCall_jiji");dynCall_jiji=dynCalls["jiji"]=createExportWrapper("dynCall_jiji",4);assert(typeof wasmExports["dynCall_iidiiii"]!="undefined","missing Wasm export: dynCall_iidiiii");dynCall_iidiiii=dynCalls["iidiiii"]=createExportWrapper("dynCall_iidiiii",7);assert(typeof wasmExports["dynCall_vii"]!="undefined","missing Wasm export: dynCall_vii");dynCall_vii=dynCalls["vii"]=createExportWrapper("dynCall_vii",3);assert(typeof wasmExports["dynCall_iii"]!="undefined","missing Wasm export: dynCall_iii");dynCall_iii=dynCalls["iii"]=createExportWrapper("dynCall_iii",3);assert(typeof wasmExports["dynCall_iij"]!="undefined","missing Wasm export: dynCall_iij");dynCall_iij=dynCalls["iij"]=createExportWrapper("dynCall_iij",3);assert(typeof wasmExports["dynCall_viiii"]!="undefined","missing Wasm export: dynCall_viiii");dynCall_viiii=dynCalls["viiii"]=createExportWrapper("dynCall_viiii",5);assert(typeof wasmExports["dynCall_iiiii"]!="undefined","missing Wasm export: dynCall_iiiii");dynCall_iiiii=dynCalls["iiiii"]=createExportWrapper("dynCall_iiiii",5);assert(typeof wasmExports["dynCall_iiij"]!="undefined","missing Wasm export: dynCall_iiij");dynCall_iiij=dynCalls["iiij"]=createExportWrapper("dynCall_iiij",4);assert(typeof wasmExports["dynCall_iiiijii"]!="undefined","missing Wasm export: dynCall_iiiijii");dynCall_iiiijii=dynCalls["iiiijii"]=createExportWrapper("dynCall_iiiijii",7);assert(typeof wasmExports["dynCall_iiiiiiiii"]!="undefined","missing Wasm export: dynCall_iiiiiiiii");dynCall_iiiiiiiii=dynCalls["iiiiiiiii"]=createExportWrapper("dynCall_iiiiiiiii",9);assert(typeof wasmExports["dynCall_vi"]!="undefined","missing Wasm export: dynCall_vi");dynCall_vi=dynCalls["vi"]=createExportWrapper("dynCall_vi",2);assert(typeof wasmExports["dynCall_viiiii"]!="undefined","missing Wasm export: dynCall_viiiii");dynCall_viiiii=dynCalls["viiiii"]=createExportWrapper("dynCall_viiiii",6);assert(typeof wasmExports["dynCall_iiji"]!="undefined","missing Wasm export: dynCall_iiji");dynCall_iiji=dynCalls["iiji"]=createExportWrapper("dynCall_iiji",4);assert(typeof wasmExports["dynCall_iijii"]!="undefined","missing Wasm export: dynCall_iijii");dynCall_iijii=dynCalls["iijii"]=createExportWrapper("dynCall_iijii",5);assert(typeof wasmExports["dynCall_iiiiiii"]!="undefined","missing Wasm export: dynCall_iiiiiii");dynCall_iiiiiii=dynCalls["iiiiiii"]=createExportWrapper("dynCall_iiiiiii",7);assert(typeof wasmExports["dynCall_viiiiiii"]!="undefined","missing Wasm export: dynCall_viiiiiii");dynCall_viiiiiii=dynCalls["viiiiiii"]=createExportWrapper("dynCall_viiiiiii",8);assert(typeof wasmExports["dynCall_iiiiii"]!="undefined","missing Wasm export: dynCall_iiiiii");dynCall_iiiiii=dynCalls["iiiiii"]=createExportWrapper("dynCall_iiiiii",6);assert(typeof wasmExports["dynCall_iiiiiiii"]!="undefined","missing Wasm export: dynCall_iiiiiiii");dynCall_iiiiiiii=dynCalls["iiiiiiii"]=createExportWrapper("dynCall_iiiiiiii",8);assert(typeof wasmExports["dynCall_iijiiiiii"]!="undefined","missing Wasm export: dynCall_iijiiiiii");dynCall_iijiiiiii=dynCalls["iijiiiiii"]=createExportWrapper("dynCall_iijiiiiii",9);assert(typeof wasmExports["asyncify_start_unwind"]!="undefined","missing Wasm export: asyncify_start_unwind");_asyncify_start_unwind=createExportWrapper("asyncify_start_unwind",1);assert(typeof wasmExports["asyncify_stop_unwind"]!="undefined","missing Wasm export: asyncify_stop_unwind");_asyncify_stop_unwind=createExportWrapper("asyncify_stop_unwind",0);assert(typeof wasmExports["asyncify_start_rewind"]!="undefined","missing Wasm export: asyncify_start_rewind");_asyncify_start_rewind=createExportWrapper("asyncify_start_rewind",1);assert(typeof wasmExports["asyncify_stop_rewind"]!="undefined","missing Wasm export: asyncify_stop_rewind");_asyncify_stop_rewind=createExportWrapper("asyncify_stop_rewind",0);assert(typeof wasmExports["memory"]!="undefined","missing Wasm export: memory");memory=wasmMemory=wasmExports["memory"];assert(typeof wasmExports["__indirect_function_table"]!="undefined","missing Wasm export: __indirect_function_table");__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={__asyncjs__read_ep1,__asyncjs__read_ep2,__asyncjs__write_ep1,__asyncjs__write_ep2,__asyncjs__write_log,_abort_js:__abort_js,_localtime_js:__localtime_js,_mktime_js:__mktime_js,_tzset_js:__tzset_js,clock_time_get:_clock_time_get,emscripten_get_now:_emscripten_get_now,emscripten_resize_heap:_emscripten_resize_heap,environ_get:_environ_get,environ_sizes_get:_environ_sizes_get,fd_close:_fd_close,fd_read:_fd_read,fd_seek:_fd_seek,fd_write:_fd_write};var calledRun;function stackCheckInit(){_emscripten_stack_init();writeStackCookie()}function run(){if(runDependencies>0){dependenciesFulfilled=run;return}stackCheckInit();preRun();if(runDependencies>0){dependenciesFulfilled=run;return}function doRun(){assert(!calledRun);calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();consumedModuleProp("onRuntimeInitialized");assert(!Module["_main"],'compiled without a main, but one is present. if you added it from JS, use Module["onRuntimeInitialized"]');postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}checkStackCookie()}function checkUnflushedContent(){var oldOut=out;var oldErr=err;var has=false;out=err=x=>{has=true};try{_fflush(0);["stdout","stderr"].forEach(name=>{var info=FS.analyzePath("/dev/"+name);if(!info)return;var stream=info.object;var rdev=stream.rdev;var tty=TTY.ttys[rdev];if(tty?.output?.length){has=true}})}catch(e){}out=oldOut;err=oldErr;if(has){warnOnce("stdio streams had content in them that was not flushed. you should set EXIT_RUNTIME to 1 (see the Emscripten FAQ), or make sure to emit a newline when you printf etc.")}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})}for(const prop of Object.keys(Module)){if(!(prop in moduleArg)){Object.defineProperty(moduleArg,prop,{configurable:true,get(){abort(`Access to module property ('${prop}') is no longer possible via the module constructor argument; Instead, use the result of the module constructor.`)}})}} +;return moduleRtn}export default Module; diff --git a/packages/frontend-core/src/control/control.wasm b/packages/frontend-core/src/control/control.wasm new file mode 100755 index 0000000..086973d Binary files /dev/null and b/packages/frontend-core/src/control/control.wasm differ diff --git a/packages/frontend-core/src/control/pre.js b/packages/frontend-core/src/control/pre.js new file mode 100644 index 0000000..6bc8e8d --- /dev/null +++ b/packages/frontend-core/src/control/pre.js @@ -0,0 +1,4 @@ +// @ts-nocheck +/* eslint-disable */ + +import { write_ep1, write_ep2, read_ep1, read_ep2, write_log_js } from '@/webusb/webUsbControlApi'; diff --git a/packages/frontend-core/src/index.ts b/packages/frontend-core/src/index.ts new file mode 100644 index 0000000..092ad0c --- /dev/null +++ b/packages/frontend-core/src/index.ts @@ -0,0 +1,4 @@ +// Re-exporting all frontend core functionalities +export * from "./common"; +export * from "./services"; +export * from "./webusb"; diff --git a/packages/frontend-core/src/services/api.ts b/packages/frontend-core/src/services/api.ts new file mode 100644 index 0000000..1f928c6 --- /dev/null +++ b/packages/frontend-core/src/services/api.ts @@ -0,0 +1,82 @@ +// Minimal API helper for the frontend +// Use import.meta.env.VITE_API_URL for absolute API base, or rely on the dev proxy (/api) + +let apiBase: string | undefined = undefined; + +/** + * Set the API base URL dynamically (call from your app initialization). + * Example: setApiBase('http://localhost:3000') + */ +export function setApiBase(base?: string) { + apiBase = base; +} + +/** Determine the API base URL by checking multiple locations (manual -> global -> env for SSR). */ +function getEnvApiBase(): string | undefined { + const g = globalThis as any; + + // 1) Explicit global variable (can be set from index.html) + if (typeof g.__API_BASE__ === 'string' && g.__API_BASE__) { + return g.__API_BASE__; + } + + // 2) process.env (SSR / Node). In browsers globalThis.process is usually undefined. + const proc = g.process as any | undefined; + if (proc && proc.env) { + if (proc.env.VITE_API_URL) return proc.env.VITE_API_URL; + if (proc.env.API_URL) return proc.env.API_URL; + } + + // 3) We do not read import.meta.env directly here to stay environment-agnostic. + // If import.meta.env is required, call setApiBase(import.meta.env.VITE_API_URL) at build/runtime. + + return undefined; +} + +/** Return the current API base URL (manual -> env -> fallback '/'). */ +export function getApiBase(): string { + const base = apiBase ?? getEnvApiBase(); + return base ? String(base) : '/'; +} + +export function apiUrl(path = ''): string { + const base = getApiBase(); + // If base looks like a relative path ("/api"), ensure the leading slash and normalize slashes + if (base.startsWith('/')) { + return `${base.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`; + } + return `${base.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`; +} + +export async function apiFetch(path: string, init?: RequestInit): Promise { + const url = apiUrl(path); + const res = await fetch(url, { ...init, credentials: 'include' }); + + const contentType = res?.headers?.get("content-type"); + + if (!res.ok) { + // try to provide better error messages + let errorBody: any; + if (contentType?.includes("application/json")) { + errorBody = await res.json(); + throw new Error(`API error ${res.status}`, { cause: errorBody }); + } else { + errorBody = await res.text(); + throw new Error(`API error ${res.status}: ${errorBody}`); + } + } + + if (contentType?.includes("application/json")) { + return (await res.json()) as unknown as T; + } + + // Fallback: return raw text (useful for plain responses) + return (await res.text()) as unknown as T; +} + +export default { + setApiBase, + getApiBase, + apiUrl, + apiFetch, +}; diff --git a/packages/frontend-core/src/services/auth.ts b/packages/frontend-core/src/services/auth.ts new file mode 100644 index 0000000..5628747 --- /dev/null +++ b/packages/frontend-core/src/services/auth.ts @@ -0,0 +1,49 @@ +import { apiUrl, apiFetch } from "./api"; + +export async function login(credentials?: { username: string; password: string }) { + let res; + if (credentials) { + // Regular login + res = await apiFetch("/api/auth/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ ...credentials }) + }); + } else { + // Guest login + res = await apiFetch("/api/auth/guest", { method: "POST" }); + } + + if (!res.ok) { + throw new Error(`Login failed: ${res.status}`); + } +} + +export async function logout() { + // Use sendBeacon for logout to ensure the request is sent even if the page is being unloaded + try { + navigator.sendBeacon(apiUrl('/api/auth/logout')); + } catch { + try { + apiFetch("/api/auth/logout", { method: "POST", keepalive: true }); + } catch { /* ignore */ } + } +} + +export async function getProfile() { + const res = await apiFetch("/api/auth/profile"); + + if (!res.ok) { + throw new Error(`Failed to fetch profile: ${res.status}`); + } + + const { ok, message, user } = res; + + return user; +} + +export default { + login, + logout, + getProfile, +}; \ No newline at end of file diff --git a/packages/frontend-core/src/services/index.ts b/packages/frontend-core/src/services/index.ts new file mode 100644 index 0000000..760ea01 --- /dev/null +++ b/packages/frontend-core/src/services/index.ts @@ -0,0 +1,2 @@ +export { setApiBase, getApiBase, apiUrl, apiFetch } from "./api"; +export { login, logout, getProfile } from "./auth"; \ No newline at end of file diff --git a/packages/frontend-core/src/tests/common/nngWebSocket.test.ts b/packages/frontend-core/src/tests/common/nngWebSocket.test.ts new file mode 100644 index 0000000..0c776f2 --- /dev/null +++ b/packages/frontend-core/src/tests/common/nngWebSocket.test.ts @@ -0,0 +1,174 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' +import { NngWebSocket, Protocol } from '@/common/nngWebSocket' + +/** + * Re-declare a minimal FakeWebSocket for these additional tests so they can run standalone. + */ + +// Polyfill CloseEvent in Node/Vitest environment if missing +if (typeof (globalThis as any).CloseEvent === 'undefined') { + (globalThis as any).CloseEvent = class CloseEvent extends Event { + wasClean: boolean; + code: number; + reason: string; + constructor(type: string, init?: Partial<{ wasClean: boolean; code: number; reason: string }>) { + super(type); + this.wasClean = !!init?.wasClean; + this.code = init?.code ?? 1000; + this.reason = init?.reason ?? ''; + } + }; +} + +class FakeWebSocket { + static CONNECTING = 0 + static OPEN = 1 + static CLOSING = 2 + static CLOSED = 3 + + static instances: FakeWebSocket[] = [] + + url: string + protocol: any + readyState: number + binaryType: string | undefined + lastSent: any + private listeners: Map + + constructor(url?: string, protocol?: any) { + this.url = url ?? '' + this.protocol = protocol + this.readyState = FakeWebSocket.CONNECTING + this.binaryType = 'arraybuffer' + this.lastSent = undefined + this.listeners = new Map() + FakeWebSocket.instances.push(this) + } + + addEventListener(type: string, cb: Function) { + const arr = this.listeners.get(type) ?? [] + arr.push(cb) + this.listeners.set(type, arr) + } + + removeEventListener(type: string, cb: Function) { + const arr = this.listeners.get(type) ?? [] + this.listeners.set(type, arr.filter(f => f !== cb)) + } + + send(data: any) { + this.lastSent = data + } + + close() { + this.readyState = FakeWebSocket.CLOSED + // emit a close event with wasClean true by default + this.triggerClose(new CloseEvent('close', { wasClean: true })) + } + + private trigger(type: string, ev: Event) { + const arr = this.listeners.get(type) ?? [] + for (const f of arr) { + try { f(ev) } catch { /* ignore */ } + } + } + + triggerOpen() { + this.readyState = FakeWebSocket.OPEN + this.trigger('open', new Event('open')) + } + + triggerMessage(data: any) { + const evt = new MessageEvent('message', { data }) + this.trigger('message', evt) + } + + triggerClose(ev?: Partial) { + const evt = new CloseEvent('close', { wasClean: ev?.wasClean ?? false, code: ev?.code ?? 1000, reason: ev?.reason ?? '' }) + this.trigger('close', evt) + } + + triggerError(detail?: any) { + const evt = new Event('error') + ; (evt as any).data = detail + this.trigger('error', evt) + } +} + +describe('NngWebSocket additional tests', () => { + let originalWebSocket: any + + beforeEach(() => { + originalWebSocket = (globalThis as any).WebSocket + ; (globalThis as any).WebSocket = FakeWebSocket + FakeWebSocket.instances = [] + }) + + afterEach(() => { + ; (globalThis as any).WebSocket = originalWebSocket + FakeWebSocket.instances = [] + vi.restoreAllMocks() + }) + + it('close() rejects outstanding REQ promises (Error instance & message)', async () => { + const nng = new NngWebSocket({ url: 'ws://req-close-test', protocol: Protocol.REQ, binaryType: NngWebSocket.TEXT }) + const p = nng.open() + await new Promise(r => setTimeout(r, 0)) + const fw = FakeWebSocket.instances[0] + fw!.triggerOpen() + await p + + const pending = nng.send('will-cancel') + // close the socket; outstanding promise should reject with an Error whose message includes "connection closed" + await nng.close() + await expect(pending).rejects.toThrow(/connection closed/) + expect(nng.isConnected()).toBe(false) + }) + + it('onWsError rejects pending REQ send promises with string details', async () => { + const nng = new NngWebSocket({ url: 'ws://err-send-test', protocol: Protocol.REQ, binaryType: NngWebSocket.TEXT }) + const p = nng.open() + await new Promise(r => setTimeout(r, 0)) + const fw = FakeWebSocket.instances[0] + fw!.triggerOpen() + await p + + const pending = nng.send('will-fail', 1000) + // trigger an error event containing string detail -> pending should reject with string including 'boom' + fw!.triggerError('boom') + await expect(pending).rejects.toMatch(/boom/) + }) + + it('receives plain string message for SUB and dispatches data & message events', async () => { + const nng = new NngWebSocket({ url: 'ws://sub-string', protocol: Protocol.SUB, binaryType: NngWebSocket.TEXT }) + const p = nng.open() + await new Promise(r => setTimeout(r, 0)) + const fw = FakeWebSocket.instances[0] + fw!.triggerOpen() + await p + + const got: { data?: any; messageCount: number } = { data: undefined, messageCount: 0 } + nng.addEventListener('data', (e: any) => { got.data = e.detail.data }) + nng.addEventListener('message', () => { got.messageCount++ }) + + // send a string directly via MessageEvent + fw!.triggerMessage('plain-text-msg') + await new Promise(r => setTimeout(r, 0)) + + expect(got.data).toBe('plain-text-msg') + expect(got.messageCount).toBeGreaterThanOrEqual(1) + }) + + it('non-REQ send after close rejects as not connected', async () => { + const nng = new NngWebSocket({ url: 'ws://after-close', protocol: Protocol.SUB, binaryType: NngWebSocket.ARRAYBUFFER }) + const p = nng.open() + await new Promise(r => setTimeout(r, 0)) + const fw = FakeWebSocket.instances[0] + fw!.triggerOpen() + await p + + // close underlying websocket + await nng.close() + await expect(nng.send(new Uint8Array([1, 2, 3]).buffer)).rejects.toMatch(/not connected/) + }) +}) diff --git a/packages/frontend-core/src/tests/services/api.test.ts b/packages/frontend-core/src/tests/services/api.test.ts new file mode 100644 index 0000000..835c4ad --- /dev/null +++ b/packages/frontend-core/src/tests/services/api.test.ts @@ -0,0 +1,168 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { apiFetch, setApiBase } from '@/services/api'; + +describe('apiFetch', () => { + let originalFetch: typeof fetch | undefined; + + // const apiBase = 'http://localhost:3000'; + const apiBase = '/api/v1'; + + beforeEach(() => { + originalFetch = (globalThis as any).fetch; + // Ensure a predictable base for tests unless a test overrides it explicitly + setApiBase(apiBase); + }); + + afterEach(() => { + // restore original fetch and clear manual base + (globalThis as any).fetch = originalFetch; + setApiBase(undefined); + vi.clearAllMocks(); + }); + + it('resolves with parsed JSON on successful response and calls fetch with normalized URL', async () => { + const mockJson = { hello: 'world' }; + const mockResponse = { + ok: true, + status: 200, + headers: new Headers({ 'content-type': 'application/text' }), + json: vi.fn().mockResolvedValue(mockJson), + text: vi.fn().mockResolvedValue('OK'), + }; + (globalThis as any).fetch = vi.fn().mockResolvedValue(mockResponse); + + const res = await apiFetch('/test/path'); + expect(res).toEqual('OK'); + expect((globalThis as any).fetch).toHaveBeenCalledTimes(1); + expect((globalThis as any).fetch).toHaveBeenCalledWith(`${apiBase}/test/path`, { credentials: 'include' }); + }); + + it('resolves with parsed JSON on successful response and calls fetch with normalized URL (JSON)', async () => { + const mockJson = { hello: 'world' }; + const mockResponse = { + ok: true, + status: 200, + headers: new Headers({ 'content-type': 'application/json' }), + json: vi.fn().mockResolvedValue(mockJson), + text: vi.fn().mockResolvedValue('OK'), + }; + (globalThis as any).fetch = vi.fn().mockResolvedValue(mockResponse); + + const res = await apiFetch('/test/path'); + expect(res).toEqual(mockJson); + expect((globalThis as any).fetch).toHaveBeenCalledTimes(1); + expect((globalThis as any).fetch).toHaveBeenCalledWith(`${apiBase}/test/path`, { credentials: 'include' }); + }); + + it('throws an Error with status and text when response is not ok', async () => { + const mockResponse = { + ok: false, + status: 500, + json: vi.fn(), + text: vi.fn().mockResolvedValue('server error'), + }; + (globalThis as any).fetch = vi.fn().mockResolvedValue(mockResponse); + + await expect(apiFetch('/fail')).rejects.toThrow('API error 500: server error'); + expect((globalThis as any).fetch).toHaveBeenCalledWith(`${apiBase}/fail`, { credentials: 'include' }); + }); + + it('throws an Error with status and text when response is not ok (JSON)', async () => { + const mockResponse = { + ok: false, + status: 500, + headers: new Headers({ 'content-type': 'application/json' }), + json: vi.fn().mockResolvedValue({ error: 'server error' }), + text: vi.fn().mockResolvedValue('server error'), + }; + (globalThis as any).fetch = vi.fn().mockResolvedValue(mockResponse); + + await expect(apiFetch('/fail')).rejects.toThrowError(new Error('API error 500', { cause: { error: 'server error' } })); + + expect((globalThis as any).fetch).toHaveBeenCalledWith(`${apiBase}/fail`, { credentials: 'include' }); + }); + + it('normalizes base and path slashes when using an absolute api base', async () => { + setApiBase('https://api.example.com'); // trailing slash + const mockJson = { ok: true }; + const mockResponse = { + ok: true, + status: 200, + headers: new Headers({ 'content-type': 'application/json' }), + json: vi.fn().mockResolvedValue(mockJson), + text: vi.fn().mockResolvedValue('OK'), + }; + (globalThis as any).fetch = vi.fn().mockResolvedValue(mockResponse); + + const res = await apiFetch('/foo'); // leading slash + expect(res).toEqual(mockJson); + expect((globalThis as any).fetch).toHaveBeenCalledWith('https://api.example.com/foo', { credentials: 'include' }); + }); + +}); +// Tests for getEnvApiBase (indirectly via the public getApiBase) +describe('getEnvApiBase via getApiBase', () => { + let originalApiBaseGlobal: any; + let originalVite: string | undefined; + let originalApiUrl: string | undefined; + let getApiBaseFn: () => string; + + const restoreEnv = () => { + if (originalApiBaseGlobal === undefined) { + delete (globalThis as any).__API_BASE__; + } else { + (globalThis as any).__API_BASE__ = originalApiBaseGlobal; + } + if (originalVite === undefined) delete process.env.VITE_API_URL; + else process.env.VITE_API_URL = originalVite; + if (originalApiUrl === undefined) delete process.env.API_URL; + else process.env.API_URL = originalApiUrl; + }; + + beforeEach(async () => { + const mod = await import('@/services/api'); + getApiBaseFn = mod.getApiBase; + + originalApiBaseGlobal = (globalThis as any).__API_BASE__; + originalVite = process.env.VITE_API_URL; + originalApiUrl = process.env.API_URL; + + delete (globalThis as any).__API_BASE__; + delete process.env.VITE_API_URL; + delete process.env.API_URL; + + setApiBase(undefined); // ensure manual base does not override env/global + }); + + afterEach(() => { + restoreEnv(); + setApiBase(undefined); + }); + + it('prefers global __API_BASE__ over env vars', () => { + (globalThis as any).__API_BASE__ = 'https://global.example'; + process.env.VITE_API_URL = 'https://vite.example'; + process.env.API_URL = 'https://api.example'; + expect(getApiBaseFn()).toBe('https://global.example'); + }); + + it('uses process.env.VITE_API_URL when __API_BASE__ is not set', () => { + process.env.VITE_API_URL = 'https://vite.example'; + expect(getApiBaseFn()).toBe('https://vite.example'); + }); + + it('falls back to process.env.API_URL when VITE_API_URL is not set', () => { + process.env.API_URL = 'https://api.example'; + expect(getApiBaseFn()).toBe('https://api.example'); + }); + + it('returns "/" when neither global nor env vars are set', () => { + expect(getApiBaseFn()).toBe('/'); + }); + + it('ignores empty string __API_BASE__ and uses env', () => { + (globalThis as any).__API_BASE__ = ''; + process.env.VITE_API_URL = 'https://vite.example'; + expect(getApiBaseFn()).toBe('https://vite.example'); + }); +}); diff --git a/packages/frontend-core/src/tests/services/auth.test.ts b/packages/frontend-core/src/tests/services/auth.test.ts new file mode 100644 index 0000000..1328651 --- /dev/null +++ b/packages/frontend-core/src/tests/services/auth.test.ts @@ -0,0 +1,165 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { login } from "@/services/auth"; +import { apiFetch } from "@/services/api"; + +// Mock the @/services/api module used by auth.ts +vi.mock("@/services/api", () => ({ + apiFetch: vi.fn(), + apiUrl: vi.fn((p: string) => p), +})); + + +describe("login", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("performs regular login with credentials", async () => { + vi.mocked(apiFetch).mockResolvedValueOnce({ ok: true, status: 200 } as any); + + const credentials = { username: "user", password: "pass" }; + await expect(login(credentials)).resolves.toBeUndefined(); + + expect(apiFetch).toHaveBeenCalledTimes(1); + expect(apiFetch).toHaveBeenCalledWith("/api/auth/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(credentials), + }); + }); + + it("performs guest login when no credentials are provided", async () => { + vi.mocked(apiFetch).mockResolvedValueOnce({ ok: true, status: 200 } as any); + + await expect(login()).resolves.toBeUndefined(); + + expect(apiFetch).toHaveBeenCalledTimes(1); + expect(apiFetch).toHaveBeenCalledWith("/api/auth/guest", { method: "POST" }); + }); + + it("throws an error when regular login fails", async () => { + vi.mocked(apiFetch).mockResolvedValueOnce({ ok: false, status: 401 } as any); + + await expect(login({ username: "bad", password: "creds" })).rejects.toThrow( + "Login failed: 401" + ); + + expect(apiFetch).toHaveBeenCalledWith("/api/auth/login", expect.any(Object)); + }); + + it("throws an error when guest login fails", async () => { + vi.mocked(apiFetch).mockResolvedValueOnce({ ok: false, status: 500 } as any); + + await expect(login()).rejects.toThrow("Login failed: 500"); + + expect(apiFetch).toHaveBeenCalledWith("/api/auth/guest", { method: "POST" }); + }); + + it("propagates network errors for regular login", async () => { + const err = new Error("Network down"); + vi.mocked(apiFetch).mockRejectedValueOnce(err); + + await expect(login({ username: "user", password: "pass" })).rejects.toThrow("Network down"); + expect(apiFetch).toHaveBeenCalledWith("/api/auth/login", expect.any(Object)); + }); + + it("propagates network errors for guest login", async () => { + const err = new Error("Fetch failed"); + vi.mocked(apiFetch).mockRejectedValueOnce(err); + + await expect(login()).rejects.toThrow("Fetch failed"); + expect(apiFetch).toHaveBeenCalledWith("/api/auth/guest", { method: "POST" }); + }); +}) +// Tests for logout and getProfile +describe("logout", () => { + let originalSendBeacon: any; + + beforeEach(() => { + vi.clearAllMocks(); + originalSendBeacon = (navigator as any).sendBeacon; + }); + + afterEach(() => { + // restore original sendBeacon + Object.defineProperty(navigator, "sendBeacon", { + value: originalSendBeacon, + configurable: true, + writable: true, + }); + }); + + it("uses navigator.sendBeacon when available", async () => { + const sendBeaconMock = vi.fn().mockReturnValue(true); + Object.defineProperty(navigator, "sendBeacon", { + value: sendBeaconMock, + configurable: true, + writable: true, + }); + + const { logout } = await import("@/services/auth"); + await expect(logout()).resolves.toBeUndefined(); + + expect(sendBeaconMock).toHaveBeenCalledTimes(1); + expect(sendBeaconMock).toHaveBeenCalledWith("/api/auth/logout"); + expect(apiFetch).not.toHaveBeenCalled(); + }); + + it("falls back to fetch when sendBeacon throws", async () => { + Object.defineProperty(navigator, "sendBeacon", { + value: vi.fn(() => { + throw new Error("sendBeacon failed"); + }), + configurable: true, + writable: true, + }); + + const { logout } = await import("@/services/auth"); + await expect(logout()).resolves.toBeUndefined(); + + expect(apiFetch).toHaveBeenCalledWith("/api/auth/logout", { + method: "POST", + keepalive: true, + }); + }); +}); + +describe("getProfile", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("returns the user when response is ok", async () => { + const user = { id: "u1", name: "Alice" }; + vi.mocked(apiFetch).mockResolvedValueOnce({ + ok: true, + status: 200, + user, + } as any); + + const { getProfile } = await import("@/services/auth"); + await expect(getProfile()).resolves.toEqual(user); + + expect(apiFetch).toHaveBeenCalledWith("/api/auth/profile"); + }); + + it("throws an error when response is not ok", async () => { + vi.mocked(apiFetch).mockResolvedValueOnce({ + ok: false, + status: 403, + } as any); + + const { getProfile } = await import("@/services/auth"); + await expect(getProfile()).rejects.toThrow("Failed to fetch profile: 403"); + + expect(apiFetch).toHaveBeenCalledWith("/api/auth/profile"); + }); + + it("propagates network errors", async () => { + const err = new Error("Network issue"); + vi.mocked(apiFetch).mockRejectedValueOnce(err); + + const { getProfile } = await import("@/services/auth"); + await expect(getProfile()).rejects.toThrow("Network issue"); + }); +}); diff --git a/packages/frontend-core/src/tests/webusb/controlWebUsb.test.ts b/packages/frontend-core/src/tests/webusb/controlWebUsb.test.ts new file mode 100644 index 0000000..c836849 --- /dev/null +++ b/packages/frontend-core/src/tests/webusb/controlWebUsb.test.ts @@ -0,0 +1,266 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { ControlWebUsb, WebUsbChannels, WebUsbDirection } from '@/webusb/controlWebUsb'; +import { WebUsbManagerMode } from '@/webusb/webUsbManager'; +import { DataType } from '@websdr/core/common'; + +describe('ControlWebUsb', () => { + let control: ControlWebUsb; + let mockWebUsbManager: any; + + beforeEach(() => { + // Mocking the Worker class + (globalThis as any).Worker = class { + constructor() { } + addEventListener(_: string, __: any) { } + removeEventListener(_: string, __: any) { } + postMessage(_: any, __?: any) { } + terminate() { } + }; + + mockWebUsbManager = { + sendCommand: vi.fn().mockResolvedValue({ result: 0 }), + getStreamStatus: vi.fn().mockResolvedValue('STOPPED'), + setStreamStatus: vi.fn().mockResolvedValue(undefined), + sendDebugCommand: vi.fn().mockResolvedValue('{}'), + }; + + control = new ControlWebUsb(); + control.setCustomWebUsbManager(mockWebUsbManager); + }); + + afterEach(async () => { + if (control.isOpen()) { + await control.close(); + } + // Delete Worker mock after test + delete (globalThis as any).Worker; + }); + + describe('constructor', () => { + it('should create instance with default parameters', () => { + const ctrl = new ControlWebUsb(); + expect(ctrl).toBeInstanceOf(ControlWebUsb); + }); + + it('should create instance with custom parameters', () => { + const ctrl = new ControlWebUsb({ + control_ep: 1, + control_rep_ep: 2, + notification_ep: 3, + type: DataType.ci16, + mode: WebUsbManagerMode.WORKER, + }); + expect(ctrl).toBeInstanceOf(ControlWebUsb); + }); + }); + + describe('open and close', () => { + it('should open connection', async () => { + await control.open(1); + expect(control.isOpen()).toBe(true); + }); + + it('should close connection', async () => { + await control.open(1); + await control.close(); + expect(control.isOpen()).toBe(false); + }); + + it('should dispatch open event', async () => { + const openHandler = vi.fn(); + control.addEventListener('open', openHandler); + await control.open(1); + expect(openHandler).toHaveBeenCalled(); + }); + + it('should dispatch close event', async () => { + const closeHandler = vi.fn(); + control.addEventListener('close', closeHandler); + await control.open(1); + await control.close(); + expect(closeHandler).toHaveBeenCalled(); + }); + }); + + describe('sendCommand', () => { + beforeEach(async () => { + await control.open(1); + }); + + it('should throw error when fd is invalid', async () => { + await control.close(); + await expect(control.sendCommand('CONNECT')).rejects.toThrow(); + }); + + it('should send CONNECT command', async () => { + await control.sendCommand('CONNECT'); + expect(mockWebUsbManager.sendCommand).toHaveBeenCalled(); + }); + + it('should send command with arguments', async () => { + await control.sendCommand('SET_RX_FREQUENCY', { frequency: 915e6 }); + expect(mockWebUsbManager.sendCommand).toHaveBeenCalledWith( + 1, + expect.objectContaining({ + req_method: 'sdr_set_rx_frequency', + req_params: expect.objectContaining({ frequency: 915e6 }), + }) + ); + }); + + it('should handle START_STREAMING command', async () => { + mockWebUsbManager.getStreamStatus.mockResolvedValue('STOPPED'); + await control.sendCommand('START_STREAMING'); + expect(mockWebUsbManager.setStreamStatus).toHaveBeenCalledWith(1, 'PREPARED'); + }); + + it('should handle STOP_STREAMING command', async () => { + mockWebUsbManager.getStreamStatus.mockResolvedValue('STARTED'); + await control.sendCommand('STOP_STREAMING'); + expect(mockWebUsbManager.setStreamStatus).toHaveBeenCalledWith(1, 'STOPPED'); + }); + }); + + describe('setParameter', () => { + beforeEach(async () => { + await control.open(1); + }); + + it('should set parameter immediately when now=true', async () => { + await control.setParameter('SET_RX_GAIN', { gain: 20 }, true); + expect(mockWebUsbManager.sendCommand).toHaveBeenCalled(); + }); + + it('should defer parameter setting when now=false', async () => { + await control.setParameter('SET_RX_GAIN', { gain: 20 }, false); + expect(mockWebUsbManager.sendCommand).toHaveBeenCalled(); + }); + + it('should handle function arguments', async () => { + await control.setParameter('SET_RX_GAIN', () => ({ gain: 25 }), true); + expect(mockWebUsbManager.sendCommand).toHaveBeenCalled(); + }); + }); + + describe('setSdrParameter and getSdrParameter', () => { + beforeEach(async () => { + await control.open(1); + }); + + it('should set SDR parameter', async () => { + mockWebUsbManager.sendCommand.mockResolvedValue({ result: 0 }); + await control.setSdrParameter('test.path', 42); + expect(mockWebUsbManager.sendCommand).toHaveBeenCalledWith( + 1, + expect.objectContaining({ + req_method: 'sdr_set_parameter', + req_params: expect.objectContaining({ path: 'test.path', value: 42 }), + }) + ); + }); + + it('should throw error when set parameter fails', async () => { + mockWebUsbManager.sendCommand.mockResolvedValue({ result: -1 }); + await expect(control.setSdrParameter('test.path', 42)).rejects.toThrow(); + }); + + it('should get SDR parameter', async () => { + mockWebUsbManager.sendCommand.mockResolvedValue({ + result: 0, + details: { path: 'test.path', value: 100 }, + }); + const value = await control.getSdrParameter('test.path'); + expect(value).toBe(100); + }); + + it('should throw error when get parameter fails', async () => { + mockWebUsbManager.sendCommand.mockResolvedValue({ result: -1 }); + await expect(control.getSdrParameter('test.path')).rejects.toThrow(); + }); + }); + + describe('getDeviceInfo', () => { + beforeEach(async () => { + await control.open(1); + }); + + it('should return device info', async () => { + mockWebUsbManager.sendCommand.mockResolvedValue({ + result: 0, + details: { + device: 'TestDevice', + devid: '12345', + devrev: 'v1', + revision: '1.0.0', + }, + }); + const info = await control.getDeviceInfo(); + expect(info.device).toBe('TestDevice'); + expect(info.deviceId).toBe('12345'); + }); + + it('should return empty info when non-strict and error occurs', async () => { + mockWebUsbManager.sendCommand.mockResolvedValue({ result: -1 }); + const info = await control.getDeviceInfo(false); + expect(info.device).toBe(''); + }); + + it('should throw when strict and error occurs', async () => { + mockWebUsbManager.sendCommand.mockResolvedValue({ result: -1 }); + await expect(control.getDeviceInfo(true)).rejects.toThrow(); + }); + }); + + describe('calibrate', () => { + beforeEach(async () => { + await control.open(1); + }); + + it('should calibrate TX only', async () => { + await control.calibrate('tx'); + expect(mockWebUsbManager.sendCommand).toHaveBeenCalledWith( + 1, + expect.objectContaining({ + req_method: 'sdr_calibrate', + req_params: expect.objectContaining({ param: 10 }), + }) + ); + }); + + it('should calibrate RX only', async () => { + await control.calibrate('rx'); + expect(mockWebUsbManager.sendCommand).toHaveBeenCalledWith( + 1, + expect.objectContaining({ + req_method: 'sdr_calibrate', + req_params: expect.objectContaining({ param: 5 }), + }) + ); + }); + + it('should calibrate both TX and RX', async () => { + await control.calibrate('trx'); + expect(mockWebUsbManager.sendCommand).toHaveBeenCalledWith( + 1, + expect.objectContaining({ + req_method: 'sdr_calibrate', + req_params: expect.objectContaining({ param: 15 }), + }) + ); + }); + }); + + describe('getStreamStatus', () => { + it('should return INVALID when not open', async () => { + const status = await control.getStreamStatus(); + expect(status).toBe('INVALID'); + }); + + it('should return stream status when open', async () => { + await control.open(1); + mockWebUsbManager.getStreamStatus.mockResolvedValue('STARTED'); + const status = await control.getStreamStatus(); + expect(status).toBe('STARTED'); + }); + }); +}); \ No newline at end of file diff --git a/packages/frontend-core/src/tests/webusb/webUsb.test.ts b/packages/frontend-core/src/tests/webusb/webUsb.test.ts new file mode 100644 index 0000000..442fb02 --- /dev/null +++ b/packages/frontend-core/src/tests/webusb/webUsb.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect } from 'vitest'; +import { getDeviceHash, getWebUsbInstance, SDRDevicesIds } from '@/webusb/webUsb'; +import { WebUsbUsdr, WebUsbXsdr } from '@/webusb/webUsbWsdr'; +import { WebUsbLimeSdr } from '@/webusb/webUsbLimeSdr'; +import type { WebUsbParams } from '@/webusb/webUsbBase'; + +const dummyParams = {} as WebUsbParams; + +describe('webUsb utilities', () => { + it('getDeviceHash: undefined -> 0 and deterministic unsigned value', () => { + expect(getDeviceHash(undefined)).toBe(0); + const h = getDeviceHash(SDRDevicesIds[0]); + // value is a non-negative integer + expect(Number.isInteger(h)).toBe(true); + expect(h).toBeGreaterThanOrEqual(0); + }); + + it('getWebUsbInstance returns correct constructor instance for known devices', () => { + const h2 = getDeviceHash(SDRDevicesIds[2]); + const inst2 = getWebUsbInstance(h2, dummyParams); + expect(inst2).toBeDefined(); + expect(inst2).toBeInstanceOf(WebUsbUsdr); + + const h3 = getDeviceHash(SDRDevicesIds[3]); + const inst3 = getWebUsbInstance(h3, dummyParams); + expect(inst3).toBeDefined(); + expect(inst3).toBeInstanceOf(WebUsbXsdr); + + const h4 = getDeviceHash(SDRDevicesIds[4]); + const inst4 = getWebUsbInstance(h4, dummyParams); + expect(inst4).toBeDefined(); + expect(inst4).toBeInstanceOf(WebUsbLimeSdr); + }); + + it('getWebUsbInstance returns undefined for unknown key', () => { + const inst = getWebUsbInstance(0xdeadbeef, dummyParams); + expect(inst).toBeUndefined(); + }); +}); \ No newline at end of file diff --git a/packages/frontend-core/src/tests/webusb/webUsb.worker.test.ts b/packages/frontend-core/src/tests/webusb/webUsb.worker.test.ts new file mode 100644 index 0000000..3adec99 --- /dev/null +++ b/packages/frontend-core/src/tests/webusb/webUsb.worker.test.ts @@ -0,0 +1,243 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +// Mock dependencies used by the worker +vi.mock('@/webusb/webUsbDeviceManager', () => { + let initialized = false; + return { + initControl: vi.fn(async () => { initialized = true; }), + isControlInitialized: vi.fn(() => initialized), + }; +}); + +vi.mock('@/webusb/webUsbBase', () => ({ + WebUsb: { + getDeviceName: (device: any) => device?.name ?? 'dev', + }, +})); + +type Manager = { + open: (vid: number, pid: number) => Promise; + close: (fd: number) => Promise; + closeAll: () => Promise; + getDevice: (fd: number) => any | undefined; + getDevices: () => number[]; +}; + +const createDev = (fd: number, vid: number, pid: number, opts?: Partial<{ + name: string; + serial: string; + rxCount: number; + cmdResult: any; + dbgResult: any; + submit: any; + bytesWritten: number; + streamStatus: any; + config: any; + deviceName: string; +}>) => { + return { + fd, + vid, + pid, + device: { name: opts?.deviceName ?? `Device-${fd}` }, + getName: vi.fn(() => opts?.name ?? `dev-${fd}`), + getSerialNumber: vi.fn(() => opts?.serial ?? `SN-${fd}`), + getRXSamplesCount: vi.fn((samples?: number) => (opts?.rxCount ?? samples ?? 0)), + sendCommand: vi.fn().mockResolvedValue(opts?.cmdResult ?? 'CMD_OK'), + sendDebugCommand: vi.fn().mockResolvedValue(opts?.dbgResult ?? 'DBG_OK'), + submitRxPacket: vi.fn().mockResolvedValue(opts?.submit ?? true), + sendTxPacket: vi.fn().mockResolvedValue({ usbOutTransferResult: { bytesWritten: opts?.bytesWritten ?? 5 } }), + getStreamStatus: vi.fn(() => opts?.streamStatus ?? { running: false }), + setStreamStatus: vi.fn(), + getConfiguration: vi.fn(() => opts?.config ?? { cfg: 1 }), + }; +}; + +const createManager = (devsInit?: Record): Manager => { + const devs = new Map( + devsInit ? Object.entries(devsInit).map(([k, v]) => [Number(k), v]) : [], + ); + return { + open: vi.fn(async (vid: number, pid: number) => { + const fd = devs.size ? Math.max(...Array.from(devs.keys())) + 1 : 1; + const dev = createDev(fd, vid, pid); + devs.set(fd, dev); + return dev; + }), + close: vi.fn(async (fd: number) => devs.delete(fd)), + closeAll: vi.fn(async () => { devs.clear(); }), + getDevice: vi.fn((fd: number) => devs.get(fd)), + getDevices: vi.fn(() => Array.from(devs.keys())), + }; +}; + +let messages: any[] = []; + +async function loadWorker(manager?: Manager) { + vi.resetModules(); + messages = []; + (globalThis as any).self = globalThis as any; + (globalThis as any).postMessage = vi.fn((m: any) => { messages.push(m); }); + if (manager !== undefined) { + (globalThis as any).webUsbDeviceManager = manager; + } else { + delete (globalThis as any).webUsbDeviceManager; + } + vi.spyOn(console, 'error').mockImplementation(() => { }); + await import('@/webusb/webUsb.worker.js'); +} + +async function sendMessage(msg: any) { + const handler = (globalThis as any).onmessage; + expect(typeof handler).toBe('function'); + await handler({ data: msg } as any); + await Promise.resolve(); + await Promise.resolve(); + return messages.slice(); +} + +describe('webUsb.worker', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('responds with error on invalid message', async () => { + await loadWorker(createManager()); + const res = await sendMessage({ id: 1 }); + expect(res[0]).toMatchObject({ type: undefined, id: 1, res: 'error' }); + expect(res[0].err).toBe('invalid message'); + }); + + it('START initializes control and responds ok', async () => { + await loadWorker(createManager()); + const res = await sendMessage({ type: 'START', id: 's1' }); + expect(res[0]).toMatchObject({ type: 'START', id: 's1', res: 'ok' }); + }); + + it('OPEN success returns fd', async () => { + const mgr = createManager(); + await loadWorker(mgr); + const res = await sendMessage({ type: 'OPEN', id: 'o1', vendorId: 0x1234, productId: 0xabcd }); + expect(res[0].res).toBe('ok'); + expect(res[0].ret).toBeGreaterThan(0); + }); + + it('OPEN error returns -1 and error message', async () => { + const mgr: Manager = { + ...createManager(), + open: vi.fn(async () => undefined), + }; + await loadWorker(mgr); + const vid = 0xBEEF, pid = 0xCAFE; + const res = await sendMessage({ type: 'OPEN', id: 'o2', vendorId: vid, productId: pid }); + expect(res[0]).toMatchObject({ type: 'OPEN', id: 'o2', res: 'error', ret: -1 }); + expect(String(res[0].err)).toContain(`vid = 0x${vid.toString(16)}`); + expect(String(res[0].err)).toContain(`pid = 0x${pid.toString(16)}`); + }); + + it('errors when manager is missing for device operations', async () => { + await loadWorker(undefined); + const res = await sendMessage({ type: 'GET_DEV_NAME', id: 10, fd: 1 }); + expect(res[0].res).toBe('error'); + expect(res[0].err).toBe('webUsbDeviceManager is not defined'); + }); + + it('GET_DEV_NAME errors when device not found', async () => { + const mgr = createManager(); + mgr.getDevice = vi.fn(() => undefined); + await loadWorker(mgr); + const res = await sendMessage({ type: 'GET_DEV_NAME', id: 'n1', fd: 99 }); + expect(res[0].res).toBe('error'); + expect(String(res[0].err)).toContain('error getting a webusb device (fd = 99)'); + }); + + it('GET_DEV_NAME returns device name', async () => { + const dev = createDev(7, 0x1, 0x2, { name: 'Device A' }); + const mgr = createManager({ 7: dev }); + await loadWorker(mgr); + const res = await sendMessage({ type: 'GET_DEV_NAME', id: 'n2', fd: 7 }); + expect(res[0]).toMatchObject({ res: 'ok', ret: 'Device A' }); + }); + + it('SEND_COMMAND resolves and responds ok with payload', async () => { + const dev = createDev(3, 0x1, 0x2, { cmdResult: { a: 1 } }); + const mgr = createManager({ 3: dev }); + await loadWorker(mgr); + const res = await sendMessage({ type: 'SEND_COMMAND', id: 'c1', fd: 3, req: { cmd: 1 } }); + expect(res[0]).toMatchObject({ type: 'SEND_COMMAND', id: 'c1', res: 'ok', ret: { a: 1 } }); + }); + + it('SEND_DEBUG_COMMAND rejection responds with error', async () => { + const dev = createDev(4, 0x1, 0x2); + dev.sendDebugCommand = vi.fn().mockRejectedValue(new Error('fail')); + const mgr = createManager({ 4: dev }); + await loadWorker(mgr); + const res = await sendMessage({ type: 'SEND_DEBUG_COMMAND', id: 'd1', fd: 4, req: { dbg: true } }); + expect(res[0].res).toBe('error'); + expect(String(res[0].err)).toContain('fail'); + }); + + it('GET_OPENED_DEVICE_LIST returns device infos', async () => { + const d1 = createDev(1, 11, 22, { deviceName: 'HW1' }); + const d2 = createDev(2, 33, 44, { deviceName: 'HW2' }); + const mgr = createManager({ 1: d1, 2: d2 }); + await loadWorker(mgr); + const res = await sendMessage({ type: 'GET_OPENED_DEVICE_LIST', id: 'l1' }); + expect(res[0].res).toBe('ok'); + expect(Array.isArray(res[0].ret)).toBe(true); + expect(res[0].ret).toEqual( + expect.arrayContaining([ + expect.objectContaining({ devName: 'HW1', vendorId: 11, productId: 22 }), + expect.objectContaining({ devName: 'HW2', vendorId: 33, productId: 44 }), + ]), + ); + }); + + it('SET_STREAM_STATUS sets status and echoes back', async () => { + const dev = createDev(5, 0x1, 0x2); + const mgr = createManager({ 5: dev }); + await loadWorker(mgr); + const status = { running: true }; + const res = await sendMessage({ type: 'SET_STREAM_STATUS', id: 's2', fd: 5, status }); + expect(res[0]).toMatchObject({ res: 'ok', ret: status }); + expect(dev.setStreamStatus).toHaveBeenCalledWith(status); + }); + + it('SEND_TX_PACKET returns bytesWritten only', async () => { + const dev = createDev(6, 0x1, 0x2, { bytesWritten: 10 }); + const mgr = createManager({ 6: dev }); + await loadWorker(mgr); + const res = await sendMessage({ type: 'SEND_TX_PACKET', id: 't1', fd: 6, data: new Uint8Array([1, 2, 3]), opts: {} }); + expect(res[0]).toMatchObject({ res: 'ok', ret: { bytesWritten: 10 } }); + }); + + it('GET_RX_SAMPLES_COUNT returns count', async () => { + const dev = createDev(8, 0x1, 0x2, { rxCount: 42 }); + const mgr = createManager({ 8: dev }); + await loadWorker(mgr); + const res = await sendMessage({ type: 'GET_RX_SAMPLES_COUNT', id: 'r1', fd: 8, samples: 100 }); + expect(res[0]).toMatchObject({ res: 'ok', ret: 42 }); + }); + + it('CLOSE and CLOSE_ALL respond ok', async () => { + const dev = createDev(9, 0x1, 0x2); + const mgr = createManager({ 9: dev }); + await loadWorker(mgr); + const r1 = await sendMessage({ type: 'CLOSE', id: 'cl1', fd: 9 }); + expect(r1[0]).toMatchObject({ res: 'ok', ret: true }); + + const r2 = await sendMessage({ type: 'CLOSE_ALL', id: 'cl2' }); + expect(r2[0]).toMatchObject({ res: 'ok', ret: true }); + }); + + it('unknown message type produces error via assertNever', async () => { + await loadWorker(createManager()); + const res = await sendMessage({ type: 'FOO', id: 77 }); + expect(res[0]).toMatchObject({ type: 'FOO', id: 77, res: 'error' }); + + const errStr = String(res[0].err); + expect(errStr.length).toBeGreaterThan(0); + // Accept either the explicit 'Unhandled message type' or the actual error produced in this environment + expect(errStr).toMatch(/Unhandled message type|error getting a webusb device/); + }); +}); \ No newline at end of file diff --git a/packages/frontend-core/src/tests/webusb/webUsbBase.test.ts b/packages/frontend-core/src/tests/webusb/webUsbBase.test.ts new file mode 100644 index 0000000..8c14aca --- /dev/null +++ b/packages/frontend-core/src/tests/webusb/webUsbBase.test.ts @@ -0,0 +1,192 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { WebUsb, DefaultDeviceConfiguration } from '@/webusb/webUsbBase'; +import { DataType } from '@websdr/core/common'; + +class TestWebUsb extends WebUsb { + getConfiguration() { + return DefaultDeviceConfiguration; + } + getRXSamplesCount(samples: number) { + return samples; + } + getRXPacketSize(samples: number) { + return samples * WebUsb.getSampleByteLength(DataType.ci16); + } + getTXPacketSize(samples: number) { + return samples * WebUsb.getSampleByteLength(DataType.ci16); + } + async decodeRxData(): Promise { + return Promise.resolve({ + data: new ArrayBuffer(0), + datatype: DataType.ci16, + id: 0, + samples: 0, + timestamp: 0n, + overrun: 0, + realigned: 0, + dropped: 0, + recvsize: 0, + }); + } + async encodeTxData(data: any): Promise { + return data.data ?? new ArrayBuffer(0); + } +} + +type Deferred = { + promise: Promise; + resolve: (v: T) => void; + reject: (e: any) => void; +}; +function deferred(): Deferred { + let resolve!: (v: T) => void; + let reject!: (e: any) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; +} + +function createMockModule() { + return { + _close_device: vi.fn(), + _malloc: vi.fn().mockImplementation(() => Math.floor(Math.random() * 100000) + 1), + _free: vi.fn(), + stringToAscii: vi.fn(), + AsciiToString: vi.fn().mockImplementation(() => '{"answer":"ok"}'), + ccall: vi.fn().mockResolvedValue(0), + }; +} + +function createMockUSBDevice(outs: Deferred[]) { + const transferOut = vi.fn().mockImplementation((_ep: number, buf: BufferSource) => { + const d = outs.shift(); + if (!d) return Promise.resolve({ status: 'ok', bytesWritten: (buf as ArrayBufferLike).byteLength }); + return d.promise; + }); + const transferIn = vi.fn().mockResolvedValue({ status: 'ok', data: new DataView(new ArrayBuffer(0)) }); + return { + transferOut, + transferIn, + opened: true, + close: vi.fn(), + claimInterface: vi.fn(), + selectConfiguration: vi.fn(), + configuration: { interfaces: [{ interfaceNumber: 0 }] }, + vendorId: 0x1234, + productId: 0xabcd, + manufacturerName: 'MockVendor', + productName: 'MockProduct', + serialNumber: 'SN123', + open: vi.fn(), + reset: vi.fn(), + } as unknown as USBDevice; +} + +describe('WebUsb static utilities', () => { + it('getSampleByteLength returns expected sizes', () => { + expect(WebUsb.getSampleByteLength(DataType.ci16)).toBe(4); + expect(WebUsb.getSampleByteLength(DataType.cf32)).toBe(8); + }); + + it('getSamplesCnt computes floor count by datatype', () => { + expect(WebUsb.getSamplesCnt(DataType.ci16, 400)).toBe(100); + expect(WebUsb.getSamplesCnt(DataType.cf32, 400)).toBe(50); + }); + + it('fillData copies and converts between ci16 and cf32', () => { + const f32 = new Float32Array([-1, -0.5, 0, 0.25, 0.5, 0.75, 1, -0.25]); + const i16buf = new ArrayBuffer(f32.byteLength); // same length in bytes works fine for convert helper + WebUsb.fillData(DataType.ci16, i16buf, 0, DataType.cf32, f32.buffer, f32.byteOffset, f32.length); + const i16 = new Int16Array(i16buf, 0, f32.length); + // Basic monotonic/sign checks + expect(i16.length).toBe(f32.length); + expect(i16[0]).toBeLessThan(0); + expect(i16[2]).toBe(0); + expect(i16[i16.length - 1]).toBeLessThan(0); + + // Convert back and check approximate roundtrip + const f32rtBuf = new ArrayBuffer(f32.byteLength); + WebUsb.fillData(DataType.cf32, f32rtBuf, 0, DataType.ci16, i16buf, 0, i16.length); + const f32rt = new Float32Array(f32rtBuf); + const eps = 1e-3; + for (let i = 0; i < f32.length; i++) { + expect(Math.abs(f32rt[i]! - f32[i]!)).toBeLessThanOrEqual(0.02 + eps); // allow quantization + } + }); + + it('fillData copies same-type arrays', () => { + const src = new Int16Array([1, 2, -3, 4, -5, 6]); + const dstBuf = new ArrayBuffer(src.byteLength); + WebUsb.fillData(DataType.ci16, dstBuf, 0, DataType.ci16, src.buffer, 0, src.length); + const dst = new Int16Array(dstBuf); + expect(Array.from(dst)).toEqual(Array.from(src)); + }); +}); + +describe('WebUsb command queue', () => { + it('sendCommand resolves via runCommandPool', async () => { + const module = createMockModule(); + const device = createMockUSBDevice([]); + const inst = new TestWebUsb({ fd: 1, vid: 0, pid: 0, module: module as any }); + inst.device = device; + + const p = inst.sendCommand({ cmd: 'ping' }); + const res = await p; + expect(res).toEqual({ answer: 'ok' }); + expect(module.ccall).toHaveBeenCalledWith( + 'send_command', + 'number', + ['number', 'number', 'number', 'number', 'number'], + expect.any(Array), + { async: true } + ); + }); +}); + +describe('WebUsb TX queue limits', () => { + beforeEach(() => { + (WebUsb as any).MAX_SEND_DATA_REQUEST = 2; + }); + + it('sendTxRawPacket drops when queue full and allowDrop=true', async () => { + const d1 = deferred(); + const d2 = deferred(); + const device = createMockUSBDevice([d1, d2]); + const module = createMockModule(); + const inst = new TestWebUsb({ fd: 1, vid: 0, pid: 0, module: module as any }); + inst.device = device; + + const pkt = new ArrayBuffer(64); + + const p1 = inst.sendTxRawPacket(pkt); + const p2 = inst.sendTxRawPacket(pkt); + // third should drop immediately due to full queue + await expect(inst.sendTxRawPacket(pkt, true)).rejects.toMatch(/packet dropped/); + + // Resolve first two writes + d1.resolve({ status: 'ok', bytesWritten: pkt.byteLength }); + d2.resolve({ status: 'ok', bytesWritten: pkt.byteLength }); + + await expect(p1).resolves.toMatchObject({ usbOutTransferResult: { status: 'ok' } }); + await expect(p2).resolves.toMatchObject({ usbOutTransferResult: { status: 'ok' } }); + }); + + it('waitForChangeSendDataReq resolves when a TX completes', async () => { + const d = deferred(); + const device = createMockUSBDevice([d]); + const module = createMockModule(); + const inst = new TestWebUsb({ fd: 1, vid: 0, pid: 0, module: module as any }); + inst.device = device; + + const pkt = new ArrayBuffer(16); + const pending = inst.sendTxRawPacket(pkt); // increases queued count + + const waiter = inst.waitForChangeSendDataReq(); + d.resolve({ status: 'ok', bytesWritten: pkt.byteLength }); + + await waiter; // should resolve when internal counter decremented + await expect(pending).resolves.toMatchObject({ usbOutTransferResult: { status: 'ok' } }); + }); +}); \ No newline at end of file diff --git a/packages/frontend-core/src/tests/webusb/webUsbControlApi.test.ts b/packages/frontend-core/src/tests/webusb/webUsbControlApi.test.ts new file mode 100644 index 0000000..8a3e5bb --- /dev/null +++ b/packages/frontend-core/src/tests/webusb/webUsbControlApi.test.ts @@ -0,0 +1,128 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { + write_ep1, write_ep2, read_ep1, read_ep2, write_log_js +} from '@/webusb/webUsbControlApi'; + +function mkHeap(size = 256) { + const ab = new ArrayBuffer(size); + const u8 = new Uint8Array(ab); + // fill with incremental values for easier assertions + for (let i = 0; i < u8.length; i++) u8[i] = i & 0xff; + return { ab, u8 }; +} + +describe('webUsbControlApi', () => { + let origMgr: any; + let consoleLogSpy: any; + + beforeEach(() => { + vi.resetModules(); + origMgr = (globalThis as any).webUsbDeviceManager; + consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { }); + }); + + afterEach(() => { + (globalThis as any).webUsbDeviceManager = origMgr; + delete (globalThis as any).debug_mode; + vi.restoreAllMocks(); + }); + + it('write_ep1 writes buffer slice and returns length', async () => { + const { ab, u8 } = mkHeap(64); + const writes: any[] = []; + const dev = { + module: { HEAPU8: u8 }, + write: (_ep: number, data: BufferSource) => { + writes.push({ ep: _ep, data: new Uint8Array(data as ArrayBuffer) }); + return Promise.resolve(); + }, + }; + (globalThis as any).webUsbDeviceManager = { getDevice: () => dev }; + + const res = await write_ep1(1, 8, 10); + expect(res).toBe(10); + expect(writes.length).toBe(1); + const sent = writes[0].data; + // sent should equal heap bytes [8..17] + for (let i = 0; i < 10; i++) expect(sent[i]).toBe((8 + i) & 0xff); + }); + + it('write_ep2 writes buffer slice and returns length', async () => { + const { ab, u8 } = mkHeap(64); + const writes: any[] = []; + const dev = { + module: { HEAPU8: u8 }, + write: (_ep: number, data: BufferSource) => { + writes.push({ ep: _ep, data: new Uint8Array(data as ArrayBuffer) }); + return Promise.resolve(); + }, + }; + (globalThis as any).webUsbDeviceManager = { getDevice: () => dev }; + + const res = await write_ep2(2, 16, 8); + expect(res).toBe(8); + expect(writes.length).toBe(1); + const sent = writes[0].data; + for (let i = 0; i < 8; i++) expect(sent[i]).toBe((16 + i) & 0xff); + }); + + it('read_ep1 copies data into HEAP and returns bytes read', async () => { + const { ab, u8 } = mkHeap(128); + // prepare read payload of 6 bytes [1,2,3,4,5,6] + const payload = new Uint8Array([1, 2, 3, 4, 5, 6]).buffer; + const dev = { + module: { HEAPU8: u8 }, + read: (_ep: number, _len: number) => Promise.resolve({ status: 'ok', data: new DataView(payload) }), + }; + (globalThis as any).webUsbDeviceManager = { getDevice: () => dev }; + + const bytes = await read_ep1(1, 10, 6); + expect(bytes).toBe(6); + // verify that heap bytes at offset 10..15 equal payload + for (let i = 0; i < 6; i++) expect(u8[10 + i]).toBe([1, 2, 3, 4, 5, 6][i]); + }); + + it('read_ep1 returns -22 when read returns non-ok', async () => { + const { u8 } = mkHeap(64); + const dev = { + module: { HEAPU8: u8 }, + read: () => Promise.resolve({ status: 'stall' }), + }; + (globalThis as any).webUsbDeviceManager = { getDevice: () => dev }; + + const r = await read_ep1(1, 0, 8); + expect(r).toBe(-22); + }); + + it('read_ep2 copies data into HEAP and returns bytes read', async () => { + const { ab, u8 } = mkHeap(64); + const payload = new Uint8Array([9, 8, 7, 6]).buffer; + const dev = { + module: { HEAPU8: u8 }, + read: (_ep: number, _len: number) => Promise.resolve({ status: 'ok', data: new DataView(payload) }), + }; + (globalThis as any).webUsbDeviceManager = { getDevice: () => dev }; + + const bytes = await read_ep2(2, 4, 4); + expect(bytes).toBe(4); + for (let i = 0; i < 4; i++) expect(u8[4 + i]).toBe([9, 8, 7, 6][i]); + }); + + it('read_ep2 returns -22 when device missing read or module', async () => { + (globalThis as any).webUsbDeviceManager = { getDevice: () => undefined }; + const r = await read_ep2(1, 0, 4); + // missing device => function returns 0 according to implementation + expect(r).toBe(0); + }); + + it('write_log_js calls AsciiToString and logs when debug_mode=true', async () => { + const dev = { + module: { AsciiToString: (ptr: number) => `STR@${ptr}` }, + }; + (globalThis as any).webUsbDeviceManager = { getDevice: () => dev }; + (globalThis as any).debug_mode = true; + const res = await write_log_js(1, 2, 123); + expect(res).toBe(0); + expect(consoleLogSpy).toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/packages/frontend-core/src/tests/webusb/webUsbDeviceManager.test.ts b/packages/frontend-core/src/tests/webusb/webUsbDeviceManager.test.ts new file mode 100644 index 0000000..53bfce8 --- /dev/null +++ b/packages/frontend-core/src/tests/webusb/webUsbDeviceManager.test.ts @@ -0,0 +1,106 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; + +describe('WebUsbDeviceManager', () => { + let mod: typeof import('@/webusb/webUsbDeviceManager'); + const fakeControlModule = { foo: 'bar' }; + + beforeEach(async () => { + vi.resetModules(); + + // mock webUsb helpers used by the manager + vi.mock('@/webusb/webUsb', () => { + return { + getDeviceHash: (_: any) => 'DEV_HASH', + getWebUsbInstance: (_hash: string, params: any) => { + // return a minimal fake WebUsb instance + return { + fd: params.fd, + vid: params.vid, + pid: params.pid, + open: vi.fn(async () => { /* opened */ }), + close: vi.fn(async () => { /* closed */ }), + }; + }, + }; + }); + + // mock control init module for initControl() + vi.mock('@/control/control', async () => { + return { + // return a stable fake control module directly to avoid closure/scope issues + default: async (overrides?: any) => ({ foo: 'bar', ...overrides }), + }; + }); + + // import module under test after mocks are set + mod = await import('@/webusb/webUsbDeviceManager'); + }); + + afterEach(() => { + // cleanup globals to avoid test leakage + delete (globalThis as any).controlModule; + delete (globalThis as any).webUsbDeviceManager; + vi.restoreAllMocks(); + }); + + it('initControl sets global controlModule and webUsbDeviceManager', async () => { + expect((globalThis as any).controlModule).toBeUndefined(); + expect((globalThis as any).webUsbDeviceManager).toBeUndefined(); + + await mod.initControl(); + expect((globalThis as any).controlModule).toBeDefined(); + expect((globalThis as any).controlModule).toMatchObject(fakeControlModule); + expect((globalThis as any).webUsbDeviceManager).toBeInstanceOf(mod.WebUsbDeviceManager); + expect(mod.isControlInitialized()).toBe(true); + }); + + it('open creates new device at slot 0 and calls open', async () => { + const mgr = new mod.WebUsbDeviceManager({ module: fakeControlModule as any }); + // first open should create device at fd=0 + const dev = await mgr.open(0x1111, 0x2222); + expect(dev).toBeDefined(); + expect(dev!.fd).toBe(0); + // second open with same vid/pid should return same instance + const dev2 = await mgr.open(0x1111, 0x2222); + expect(dev2).toBe(dev); + }); + + it('open reuses first undefined slot if present', async () => { + const mgr = new mod.WebUsbDeviceManager({ module: fakeControlModule as any }); + // create one device to grow internal array + const first = await mgr.open(1, 2); + // manually create an undefined gap at index 1 and a defined device at index 2 + (mgr as any).webUsbDevices = [first, undefined, { fd: 2, vid: 9, pid: 9, open: vi.fn(async () => { }) }]; + // open new device should use slot index 1 (first undefined) + const newDev = await mgr.open(3, 4); + expect(newDev).toBeDefined(); + expect(newDev!.fd).toBe(1); + }); + + it('close returns false for invalid fd and true for valid', async () => { + const mgr = new mod.WebUsbDeviceManager({ module: fakeControlModule as any }); + // nothing opened => close invalid fd + expect(await mgr.close(-1)).toBe(false); + expect(await mgr.close(0)).toBe(false); + + const dev = await mgr.open(10, 11); + expect(dev).toBeDefined(); + const ok = await mgr.close(dev!.fd); + expect(ok).toBe(true); + // after close device slot should be undefined + expect(mgr.getDevice(dev!.fd)).toBeUndefined(); + }); + + it('getDevice and getDevices reflect current state', async () => { + const mgr = new mod.WebUsbDeviceManager({ module: fakeControlModule as any }); + const d0 = await mgr.open(0xa, 0xb); + const d1 = await mgr.open(0xc, 0xd); + const devices = mgr.getDevices(); + expect(Array.isArray(devices)).toBe(true); + expect(devices).toContain(d0!.fd); + expect(devices).toContain(d1!.fd); + + // out-of-range returns undefined + expect(mgr.getDevice(999)).toBeUndefined(); + }); +}); \ No newline at end of file diff --git a/packages/frontend-core/src/tests/webusb/webUsbLimeSdr.test.ts b/packages/frontend-core/src/tests/webusb/webUsbLimeSdr.test.ts new file mode 100644 index 0000000..aa9f51e --- /dev/null +++ b/packages/frontend-core/src/tests/webusb/webUsbLimeSdr.test.ts @@ -0,0 +1,93 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { WebUsbLimeSdr } from '@/webusb/webUsbLimeSdr'; +import { DataType, COMPLEX_INT16_SIZE } from '@websdr/core/common'; + +const mkParams = () => ({ fd: 0, vid: 0x1111, pid: 0x2222, module: {} as any }); + +describe('WebUsbLimeSdr', () => { + let inst: WebUsbLimeSdr; + + beforeEach(() => { + // stub navigator safely in Node test env + vi.stubGlobal('navigator', { usb: { addEventListener: () => { }, removeEventListener: () => { }, getDevices: async () => [] } } as any); + inst = new WebUsbLimeSdr(mkParams()); + }); + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('_fillHeader throws for too small buffer and for non-multiple-of-4 samples', () => { + const small = new ArrayBuffer(WebUsbLimeSdr.HEADER_SIZE - 1); + expect(() => (inst as any)._fillHeader(small, 0, false, BigInt(1), 4)).toThrow(); + + const okBuf = new ArrayBuffer(WebUsbLimeSdr.HEADER_SIZE); + expect(() => (inst as any)._fillHeader(okBuf, 0, false, BigInt(1), 3)).toThrow(/multiple of 4/); + }); + + it('_fillHeader writes expected header fields', () => { + const buf = new ArrayBuffer(WebUsbLimeSdr.HEADER_SIZE); + const ts = BigInt(0x1122334455667788n); + (inst as any)._fillHeader(buf, 0, true, ts, 4); + + const dv = new DataView(buf); + // discard flag in high nibble -> 1 << 4 = 0x10 + expect(dv.getUint8(0)).toBe(0x10); + // samples * COMPLEX_INT16_SIZE + expect(dv.getUint16(1, true)).toBe(4 * COMPLEX_INT16_SIZE); + // timestamp written at offset 8 + expect(dv.getBigUint64(8, true)).toBe(ts); + }); + + it('packet size / samples count helpers are consistent', () => { + const spp = WebUsbLimeSdr.SAMPLES_PER_PACKET; + // one packet worth of samples -> packet size + expect(inst.getRXPacketSize(spp)).toBe(WebUsbLimeSdr.PACKET_SIZE); + // less than one packet -> aligned-down samples = 0 + expect(inst.getRXSamplesCount(spp - 1)).toBe(0); + // TX packet size for exact multiple of samples should be integer multiple + const txSize = inst.getTXPacketSize(spp * 3); + expect(txSize % WebUsbLimeSdr.PACKET_SIZE).toBe(0); + }); + + it('encodeTxData produces buffer with expected length and header present', async () => { + const samplesCnt = WebUsbLimeSdr.SAMPLES_PER_PACKET + 4; // >1 packet last partial + const byteLen = samplesCnt * COMPLEX_INT16_SIZE; + const dataBuf = new ArrayBuffer(byteLen); + const txBuf = { + datatype: DataType.ci16, + data: dataBuf, + byteLength: dataBuf.byteLength, + byteOffset: 0, + timestamp: BigInt(1234), + discard_timestamp: false, + } as any; + + const out = await inst.encodeTxData(txBuf); + const expectedSize = inst.getTXPacketSize(samplesCnt); + expect(out.byteLength).toBe(expectedSize); + + // verify first header in output + const dv = new DataView(out, 0, WebUsbLimeSdr.HEADER_SIZE); + // timestamp at offset 8 equals original timestamp + expect(dv.getBigUint64(8, true)).toBe(BigInt(1234)); + }); + + it('decodeRxData decodes single-packet buffer and returns RXBuffer', async () => { + const spp = WebUsbLimeSdr.SAMPLES_PER_PACKET; + // create a single packet buffer + const ab = new ArrayBuffer(WebUsbLimeSdr.PACKET_SIZE); + // fill header + (inst as any)._fillHeader(ab, 0, false, BigInt(1000), spp); + // fill payload with incremental bytes for simple verification + const payload = new Uint8Array(ab, WebUsbLimeSdr.HEADER_SIZE); + for (let i = 0; i < payload.length; i++) payload[i] = (i & 0xff); + + const dv = new DataView(ab); + const reqSamples = spp; + const rx = await inst.decodeRxData(dv, reqSamples, { datatype: DataType.ci16, id: 7 }); + expect(rx).toHaveProperty('data'); + expect(rx.samples).toBeGreaterThanOrEqual(reqSamples); + expect(rx.id).toBe(7); + expect(rx.recvsize).toBe(WebUsbLimeSdr.PACKET_SIZE); + }); +}); \ No newline at end of file diff --git a/packages/frontend-core/src/tests/webusb/webUsbManager.test.ts b/packages/frontend-core/src/tests/webusb/webUsbManager.test.ts new file mode 100644 index 0000000..2a91503 --- /dev/null +++ b/packages/frontend-core/src/tests/webusb/webUsbManager.test.ts @@ -0,0 +1,480 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { WebUsbManagerMode, getWebUsbManagerInstance } from '@/webusb/webUsbManager'; +import type { WebUsbWorkerResponse } from '@/webusb/webUsb.worker.types'; + +describe('WebUsbSingleManager (unit)', () => { + let originalGlobalMgr: any; + + beforeEach(() => { + originalGlobalMgr = (globalThis as any).webUsbDeviceManager; + }); + + afterEach(() => { + (globalThis as any).webUsbDeviceManager = originalGlobalMgr; + }); + + it('open/getName/getSerial/sendCommand/submitRxPacket roundtrip (single)', async () => { + // fake device exposed by global webUsbDeviceManager + const fakeDevice = { + fd: 11, + getName: () => 'FAKE', + getSerialNumber: () => 'S123', + getRXSamplesCount: (samples: number) => samples + 1, + sendCommand: async (req: any) => ({ ok: true, in: req }), + sendDebugCommand: async (req: string) => `DBG:${req}`, + submitRxPacket: async (samples: number) => ({ samples }), + }; + (globalThis as any).webUsbDeviceManager = { + open: async (_v: number, _p: number) => Promise.resolve(fakeDevice), + close: async (_fd: number) => { }, + closeAll: async () => { }, + getDevice: (_fd: number) => fakeDevice, + getDevices: () => [11], + }; + + const mgr = getWebUsbManagerInstance(WebUsbManagerMode.SINGLE); + + const fd = await mgr.open(0x1234, 0x5678); + expect(fd).toBe(11); + + const name = await mgr.getName(fd); + expect(name).toBe('FAKE'); + + const sn = await mgr.getSerialNumber(fd); + expect(sn).toBe('S123'); + + const count = await mgr.getRXSamplesCount(fd, 7); + expect(count).toBe(8); + + const cmdRes = await mgr.sendCommand(fd, { a: 1 }); + expect(cmdRes).toEqual({ ok: true, in: { a: 1 } }); + + const dbg = await mgr.sendDebugCommand(fd, 'hello'); + expect(dbg).toBe('DBG:hello'); + + const rx = await mgr.submitRxPacket(fd, 128); + expect(rx).toEqual({ samples: 128 }); + + const list = await mgr.getOpenedDeviceList(); + expect(Array.isArray(list)).toBe(true); + expect(list && list.length).toBeGreaterThanOrEqual(1); + }); +}); + +describe('WebUsbWorkerManager (integration via FakeWorker)', () => { + const originalWorker = (globalThis as any).Worker; + beforeEach(() => { }); + afterEach(() => { + (globalThis as any).Worker = originalWorker; + }); + + class FakeWorker { + posted: any[] = []; + listeners: Record = {}; + terminated = false; + + addEventListener(ev: string, cb: Function) { + this.listeners[ev] = this.listeners[ev] || []; + this.listeners[ev].push(cb); + } + removeEventListener(ev: string, cb: Function) { + this.listeners[ev] = (this.listeners[ev] || []).filter(f => f !== cb); + } + postMessage(msg: any, _transfer?: any[]) { + // record posted message + this.posted.push(msg); + } + terminate() { + this.terminated = true; + } + // helper to simulate worker -> main thread message + simulateMessage(msg: WebUsbWorkerResponse) { + const handlers = this.listeners['message'] || []; + const ev = { data: msg } as MessageEvent; + handlers.forEach(h => h(ev)); + } + addEventListenerError(_: string, cb: Function) { + this.listeners['error'] = this.listeners['error'] || []; + this.listeners['error'].push(cb); + } + } + + it('start -> open resolves on ok response', async () => { + const fw = new FakeWorker(); + // ensure fresh module load so the manager constructs with our FakeWorker + (globalThis as any).Worker = function () { return fw; }; + vi.resetModules(); + const mod = await import('@/webusb/webUsbManager'); + const mgr = mod.getWebUsbManagerInstance(WebUsbManagerMode.WORKER); + + // manager constructor calls startWorker and posts START + const startMsg = fw.posted.find(m => m.type === 'START'); + expect(startMsg).toBeDefined(); + // respond START ok + fw.simulateMessage({ type: 'START', id: startMsg!.id, res: 'ok', ret: true }); + + // call open + const openP = (mgr as any).open(0x1111, 0x2222); + const openMsg = fw.posted.find(m => m.type === 'OPEN'); + expect(openMsg).toBeDefined(); + // simulate worker response for OPEN + fw.simulateMessage({ type: 'OPEN', id: openMsg!.id, res: 'ok', ret: 42 } as WebUsbWorkerResponse); + + const fd = await openP; + expect(fd).toBe(42); + }); + + it('open rejects when worker responds error', async () => { + const fw = new FakeWorker(); + (globalThis as any).Worker = function () { return fw; }; + vi.resetModules(); + const mod = await import('@/webusb/webUsbManager'); + const mgr = mod.getWebUsbManagerInstance(WebUsbManagerMode.WORKER); + const startMsg = fw.posted.find(m => m.type === 'START'); + if (startMsg) fw.simulateMessage({ type: 'START', id: startMsg.id, res: 'ok', ret: true }); + + const p = (mgr as any).open(1, 2); + const openMsg = fw.posted.find(m => m.type === 'OPEN'); + expect(openMsg).toBeDefined(); + fw.simulateMessage({ type: 'OPEN', id: openMsg!.id, res: 'error', err: 'open failed' } as WebUsbWorkerResponse); + + await expect(p).rejects.toBeDefined(); + }); + + it('GET_OPENED_DEVICE_LIST resolves with worker payload', async () => { + const fw = new FakeWorker(); + (globalThis as any).Worker = function () { return fw; }; + vi.resetModules(); + const mod = await import('@/webusb/webUsbManager'); + const mgr = mod.getWebUsbManagerInstance(WebUsbManagerMode.WORKER); + const startMsg = fw.posted.find(m => m.type === 'START'); + if (startMsg) fw.simulateMessage({ type: 'START', id: startMsg.id, res: 'ok', ret: true }); + + const p = (mgr as any).getOpenedDeviceList(); + const qMsg = fw.posted.find(m => m.type === 'GET_OPENED_DEVICE_LIST'); + expect(qMsg).toBeDefined(); + + const payload = [{ devName: 'X', vendorId: 1, productId: 2 }]; + fw.simulateMessage({ type: 'GET_OPENED_DEVICE_LIST', id: qMsg!.id, res: 'ok', ret: payload } as WebUsbWorkerResponse); + + const res = await p; + expect(res).toEqual(payload); + }); + + it('startWorker posts STOP to old worker, START to new worker, registers handlers and resolves on ok', async () => { + // initial worker used by constructor + const fw1 = new FakeWorker(); + (globalThis as any).Worker = function () { return fw1; }; + vi.resetModules(); + const mod = await import('@/webusb/webUsbManager'); + const mgr = mod.getWebUsbManagerInstance(WebUsbManagerMode.WORKER); + + // complete initial START to avoid dangling pending promise + const initStartMsg = fw1.posted.find(m => m.type === 'START'); + expect(initStartMsg).toBeDefined(); + fw1.simulateMessage({ type: 'START', id: initStartMsg!.id, res: 'ok', ret: true }); + + // second worker for explicit startWorker call + const fw2 = new FakeWorker(); + (globalThis as any).Worker = function () { return fw2; }; + + const p = (mgr as any).startWorker(); + + // old worker received STOP + const stopMsg = fw1.posted.find(m => m.type === 'STOP'); + expect(stopMsg).toBeDefined(); + + // new worker received START and listeners are attached + const startMsgNew = fw2.posted.find(m => m.type === 'START'); + expect(startMsgNew).toBeDefined(); + expect((fw2.listeners['message'] || []).length).toBeGreaterThan(0); + expect((fw2.listeners['error'] || []).length).toBeGreaterThan(0); + + // resolve startWorker promise + fw2.simulateMessage({ type: 'START', id: startMsgNew!.id, res: 'ok', ret: true }); + await expect(p).resolves.toBeDefined(); + }); + + it('startWorker rejects when worker responds with error', async () => { + const fw1 = new FakeWorker(); + (globalThis as any).Worker = function () { return fw1; }; + vi.resetModules(); + const mod = await import('@/webusb/webUsbManager'); + const mgr = mod.getWebUsbManagerInstance(WebUsbManagerMode.WORKER); + + // complete initial START + const initStartMsg = fw1.posted.find(m => m.type === 'START'); + expect(initStartMsg).toBeDefined(); + fw1.simulateMessage({ type: 'START', id: initStartMsg!.id, res: 'ok', ret: true }); + + const fw2 = new FakeWorker(); + (globalThis as any).Worker = function () { return fw2; }; + + const p = (mgr as any).startWorker(); + const startMsgNew = fw2.posted.find(m => m.type === 'START'); + expect(startMsgNew).toBeDefined(); + + // reject + fw2.simulateMessage({ type: 'START', id: startMsgNew!.id, res: 'error', err: 'failed to start' }); + await expect(p).rejects.toBeDefined(); + }); + + it('startWorker can be called multiple times, always posting START on the latest worker', async () => { + const fw1 = new FakeWorker(); + (globalThis as any).Worker = function () { return fw1; }; + vi.resetModules(); + const mod = await import('@/webusb/webUsbManager'); + const mgr = mod.getWebUsbManagerInstance(WebUsbManagerMode.WORKER); + + // finish initial + const initStartMsg = fw1.posted.find(m => m.type === 'START'); + fw1.simulateMessage({ type: 'START', id: initStartMsg!.id, res: 'ok', ret: true }); + + // 2nd worker + const fw2 = new FakeWorker(); + (globalThis as any).Worker = function () { return fw2; }; + const p2 = (mgr as any).startWorker(); + const start2 = fw2.posted.find(m => m.type === 'START'); + expect(start2).toBeDefined(); + fw2.simulateMessage({ type: 'START', id: start2!.id, res: 'ok', ret: true }); + await expect(p2).resolves.toBeDefined(); + + // 3rd worker + const fw3 = new FakeWorker(); + (globalThis as any).Worker = function () { return fw3; }; + const p3 = (mgr as any).startWorker(); + const start3 = fw3.posted.find(m => m.type === 'START'); + expect(start3).toBeDefined(); + fw3.simulateMessage({ type: 'START', id: start3!.id, res: 'ok', ret: true }); + await expect(p3).resolves.toBeDefined(); + + // ensure previous workers saw STOP + expect(fw1.posted.some(m => m.type === 'STOP')).toBe(true); + expect(fw2.posted.some(m => m.type === 'STOP')).toBe(true); + }); + + it('proxies basic requests to worker and resolves with payloads', async () => { + const fw = new FakeWorker(); + (globalThis as any).Worker = function () { return fw; }; + vi.resetModules(); + const mod = await import('@/webusb/webUsbManager'); + const mgr = mod.getWebUsbManagerInstance(WebUsbManagerMode.WORKER); + + const startMsg = fw.posted.find(m => m.type === 'START'); + fw.simulateMessage({ type: 'START', id: startMsg!.id, res: 'ok', ret: true }); + + // getName + const pName = (mgr as any).getName(7); + const getNameMsg = fw.posted.find(m => m.type === 'GET_DEV_NAME'); + expect(getNameMsg).toBeDefined(); + fw.simulateMessage({ type: 'GET_DEV_NAME', id: getNameMsg!.id, res: 'ok', ret: 'NAME7' } as WebUsbWorkerResponse); + await expect(pName).resolves.toBe('NAME7'); + + // getSerialNumber + const pSN = (mgr as any).getSerialNumber(7); + const getSnMsg = fw.posted.find(m => m.type === 'GET_SERIAL_NUMBER' && m.id === (pSN as any)?.id) || fw.posted.find(m => m.type === 'GET_SERIAL_NUMBER'); + expect(getSnMsg).toBeDefined(); + fw.simulateMessage({ type: 'GET_SERIAL_NUMBER', id: getSnMsg!.id, res: 'ok', ret: 'SN7' } as WebUsbWorkerResponse); + await expect(pSN).resolves.toBe('SN7'); + + // getRXSamplesCount + const pCount = (mgr as any).getRXSamplesCount(9, 128); + const rxCntMsg = fw.posted.find(m => m.type === 'GET_RX_SAMPLES_COUNT'); + expect(rxCntMsg).toBeDefined(); + expect(rxCntMsg!.samples).toBe(128); + fw.simulateMessage({ type: 'GET_RX_SAMPLES_COUNT', id: rxCntMsg!.id, res: 'ok', ret: 256 } as WebUsbWorkerResponse); + await expect(pCount).resolves.toBe(256); + + // sendCommand + const pCmd = (mgr as any).sendCommand(9, { a: 1 }); + const cmdMsg = fw.posted.find(m => m.type === 'SEND_COMMAND'); + expect(cmdMsg).toBeDefined(); + expect(cmdMsg!.req).toEqual({ a: 1 }); + fw.simulateMessage({ type: 'SEND_COMMAND', id: cmdMsg!.id, res: 'ok', ret: { ok: true } } as WebUsbWorkerResponse); + await expect(pCmd).resolves.toEqual({ ok: true }); + + // sendDebugCommand + const pDbg = (mgr as any).sendDebugCommand(9, 'hello'); + const dbgMsg = fw.posted.find(m => m.type === 'SEND_DEBUG_COMMAND'); + expect(dbgMsg).toBeDefined(); + expect(dbgMsg!.req).toBe('hello'); + fw.simulateMessage({ type: 'SEND_DEBUG_COMMAND', id: dbgMsg!.id, res: 'ok', ret: 'DBG:hello' } as WebUsbWorkerResponse); + await expect(pDbg).resolves.toBe('DBG:hello'); + }); + + it('submitRxPacket transfers buffer when opts.data is ArrayBuffer, otherwise no transfer', async () => { + class FakeWorkerWithTransfer extends FakeWorker { + lastTransfer?: any[]; + postMessage(msg: any, transfer?: any[]) { + this.posted.push(msg); + this.lastTransfer = transfer; + } + } + const fw = new FakeWorkerWithTransfer(); + (globalThis as any).Worker = function () { return fw; }; + vi.resetModules(); + const mod = await import('@/webusb/webUsbManager'); + const mgr = mod.getWebUsbManagerInstance(WebUsbManagerMode.WORKER); + + const startMsg = fw.posted.find(m => m.type === 'START'); + fw.simulateMessage({ type: 'START', id: startMsg!.id, res: 'ok', ret: true }); + + // with transfer + const buf = new ArrayBuffer(16); + const p1 = (mgr as any).submitRxPacket(3, 64, { id: 1, data: buf }); + const rxMsg1 = fw.posted.find(m => m.type === 'SUBMIT_RX_PACKET'); + expect(rxMsg1).toBeDefined(); + expect(fw.lastTransfer && fw.lastTransfer.length).toBe(1); + expect(fw.lastTransfer![0]).toBe(buf); + fw.simulateMessage({ type: 'SUBMIT_RX_PACKET', id: rxMsg1!.id, res: 'ok', ret: { samples: 64 } } as WebUsbWorkerResponse); + await expect(p1).resolves.toEqual({ samples: 64 }); + + // without transfer + const p2 = (mgr as any).submitRxPacket(4, 32, { id: 2 }); + const rxMsg2 = fw.posted.filter(m => m.type === 'SUBMIT_RX_PACKET').pop(); + expect(rxMsg2).toBeDefined(); + expect(fw.lastTransfer).toBeUndefined(); + fw.simulateMessage({ type: 'SUBMIT_RX_PACKET', id: rxMsg2!.id, res: 'ok', ret: { samples: 32 } } as WebUsbWorkerResponse); + await expect(p2).resolves.toEqual({ samples: 32 }); + }); + + it('sendTxPacket transfers buffer when data.data is ArrayBuffer, otherwise no transfer', async () => { + class FakeWorkerWithTransfer extends FakeWorker { + lastTransfer?: any[]; + postMessage(msg: any, transfer?: any[]) { + this.posted.push(msg); + this.lastTransfer = transfer; + } + } + const fw = new FakeWorkerWithTransfer(); + (globalThis as any).Worker = function () { return fw; }; + vi.resetModules(); + const mod = await import('@/webusb/webUsbManager'); + const mgr = mod.getWebUsbManagerInstance(WebUsbManagerMode.WORKER); + + const startMsg = fw.posted.find(m => m.type === 'START'); + fw.simulateMessage({ type: 'START', id: startMsg!.id, res: 'ok', ret: true }); + + // with transfer + const ab = new ArrayBuffer(8); + const p1 = (mgr as any).sendTxPacket(5, { data: ab }, { mode: 'x' }); + const txMsg1 = fw.posted.find(m => m.type === 'SEND_TX_PACKET'); + expect(txMsg1).toBeDefined(); + expect(fw.lastTransfer && fw.lastTransfer.length).toBe(1); + expect(fw.lastTransfer![0]).toBe(ab); + fw.simulateMessage({ type: 'SEND_TX_PACKET', id: txMsg1!.id, res: 'ok', ret: { ok: true } } as WebUsbWorkerResponse); + await expect(p1).resolves.toEqual({ ok: true }); + + // without transfer + const p2 = (mgr as any).sendTxPacket(6, { data: { notBuffer: true } }, {}); + const txMsg2 = fw.posted.filter(m => m.type === 'SEND_TX_PACKET').pop(); + expect(txMsg2).toBeDefined(); + expect(fw.lastTransfer).toBeUndefined(); + fw.simulateMessage({ type: 'SEND_TX_PACKET', id: txMsg2!.id, res: 'ok', ret: { ok: 2 } } as WebUsbWorkerResponse); + await expect(p2).resolves.toEqual({ ok: 2 }); + }); + + it('close with negative fd returns immediately without posting CLOSE', async () => { + const fw = new FakeWorker(); + (globalThis as any).Worker = function () { return fw; }; + vi.resetModules(); + const mod = await import('@/webusb/webUsbManager'); + const mgr = mod.getWebUsbManagerInstance(WebUsbManagerMode.WORKER); + + const startMsg = fw.posted.find(m => m.type === 'START'); + fw.simulateMessage({ type: 'START', id: startMsg!.id, res: 'ok', ret: true }); + + await (mgr as any).close(-1); + expect(fw.posted.some(m => m.type === 'CLOSE')).toBe(false); + }); + + it('close and closeAll resolve on ok and reject on error', async () => { + const fw = new FakeWorker(); + (globalThis as any).Worker = function () { return fw; }; + vi.resetModules(); + const mod = await import('@/webusb/webUsbManager'); + const mgr = mod.getWebUsbManagerInstance(WebUsbManagerMode.WORKER); + + const startMsg = fw.posted.find(m => m.type === 'START'); + fw.simulateMessage({ type: 'START', id: startMsg!.id, res: 'ok', ret: true }); + + // close ok + const pClose = (mgr as any).close(10); + const closeMsg = fw.posted.find(m => m.type === 'CLOSE'); + expect(closeMsg).toBeDefined(); + fw.simulateMessage({ type: 'CLOSE', id: closeMsg!.id, res: 'ok', ret: true } as WebUsbWorkerResponse); + await expect(pClose).resolves.toBe(true); + + // closeAll reject + const pCA = (mgr as any).closeAll(); + const caMsg = fw.posted.find(m => m.type === 'CLOSE_ALL'); + expect(caMsg).toBeDefined(); + fw.simulateMessage({ type: 'CLOSE_ALL', id: caMsg!.id, res: 'error', err: 'fail' } as WebUsbWorkerResponse); + await expect(pCA).rejects.toBeDefined(); + }); + + it('get/set stream status and getConfiguration', async () => { + const fw = new FakeWorker(); + (globalThis as any).Worker = function () { return fw; }; + vi.resetModules(); + const mod = await import('@/webusb/webUsbManager'); + const mgr = mod.getWebUsbManagerInstance(WebUsbManagerMode.WORKER); + + const startMsg = fw.posted.find(m => m.type === 'START'); + fw.simulateMessage({ type: 'START', id: startMsg!.id, res: 'ok', ret: true }); + + const pSet = (mgr as any).setStreamStatus(1, 'RUNNING'); + const setMsg = fw.posted.find(m => m.type === 'SET_STREAM_STATUS'); + expect(setMsg).toBeDefined(); + expect(setMsg!.status).toBe('RUNNING'); + fw.simulateMessage({ type: 'SET_STREAM_STATUS', id: setMsg!.id, res: 'ok', ret: true } as WebUsbWorkerResponse); + await expect(pSet).resolves.toBe(true); + + const pGet = (mgr as any).getStreamStatus(1); + const getMsg = fw.posted.find(m => m.type === 'GET_STREAM_STATUS'); + expect(getMsg).toBeDefined(); + fw.simulateMessage({ type: 'GET_STREAM_STATUS', id: getMsg!.id, res: 'ok', ret: 'PREPARED' } as WebUsbWorkerResponse); + await expect(pGet).resolves.toBe('PREPARED'); + + const pCfg = (mgr as any).getConfiguration(1); + const cfgMsg = fw.posted.find(m => m.type === 'GET_CONFIGURATION'); + expect(cfgMsg).toBeDefined(); + const cfg = { a: 1 }; + fw.simulateMessage({ type: 'GET_CONFIGURATION', id: cfgMsg!.id, res: 'ok', ret: cfg } as WebUsbWorkerResponse); + await expect(pCfg).resolves.toEqual(cfg); + }); + + it('throws when worker is not running', async () => { + const fw = new FakeWorker(); + (globalThis as any).Worker = function () { return fw; }; + vi.resetModules(); + const mod = await import('@/webusb/webUsbManager'); + const mgr = mod.getWebUsbManagerInstance(WebUsbManagerMode.WORKER); + + const startMsg = fw.posted.find(m => m.type === 'START'); + fw.simulateMessage({ type: 'START', id: startMsg!.id, res: 'ok', ret: true }); + + fw.simulateMessage({ type: 'STOP', res: 'ok' } as any); + + await expect((mgr as any).getName(1)).rejects.toThrow(/worker is not running/); + }); + + it('stopWorker posts STOP and resolves on response', async () => { + const fw = new FakeWorker(); + (globalThis as any).Worker = function () { return fw; }; + vi.resetModules(); + const mod = await import('@/webusb/webUsbManager'); + const mgr = mod.getWebUsbManagerInstance(WebUsbManagerMode.WORKER); + + const startMsg = fw.posted.find(m => m.type === 'START'); + fw.simulateMessage({ type: 'START', id: startMsg!.id, res: 'ok', ret: true }); + + const p = (mgr as any).stopWorker(); + const stopMsg = fw.posted.find(m => m.type === 'STOP'); + expect(stopMsg).toBeDefined(); + fw.simulateMessage({ type: 'STOP', id: stopMsg!.id, res: 'ok', ret: true } as WebUsbWorkerResponse); + await expect(p).resolves.toBe(true); + + await expect((mgr as any).getName(1)).rejects.toThrow(/worker is not running/); + }); +}); \ No newline at end of file diff --git a/packages/frontend-core/src/tests/webusb/webUsbWsdr.test.ts b/packages/frontend-core/src/tests/webusb/webUsbWsdr.test.ts new file mode 100644 index 0000000..1f370ea --- /dev/null +++ b/packages/frontend-core/src/tests/webusb/webUsbWsdr.test.ts @@ -0,0 +1,84 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { WebUsbXsdr } from '@/webusb/webUsbWsdr'; +import { DataType, COMPLEX_INT16_SIZE } from '@websdr/core/common'; +import { WebUsb } from '@/webusb/webUsbBase'; + +const mkParams = () => ({ fd: 0, vid: 0x1111, pid: 0x2222, module: {} as any }); + +describe('WebUsbWsdr (WebUsbXsdr)', () => { + let inst: WebUsbXsdr; + + beforeEach(() => { + // safe navigator stub if ctor accesses it + vi.stubGlobal('navigator', { usb: { addEventListener: () => { }, removeEventListener: () => { }, getDevices: async () => [] } } as any); + inst = new WebUsbXsdr(mkParams()); + }); + + it('_fillHeader validates buffer length and sample range and writes fields', () => { + const small = new ArrayBuffer(WebUsbXsdr.HEADER_SIZE - 1); + expect(() => (inst as any)._fillHeader(small, 0, false, BigInt(1), WebUsbXsdr.MIN_SAMPLES_IN_PKT)).toThrow(); + + const buf = new ArrayBuffer(WebUsbXsdr.HEADER_SIZE + 8); + (inst as any)._fillHeader(buf, 0, true, BigInt(0x1122334455667788n), WebUsbXsdr.MIN_SAMPLES_IN_PKT); + const dv = new DataView(buf); + // timestamp low 32 bits + expect(dv.getUint32(0, true)).toBeDefined(); + // samples stored in header word: check non-zero + const word = dv.getUint16(6, true); + expect(word & 0x7fff).toBeGreaterThanOrEqual(WebUsbXsdr.MIN_SAMPLES_IN_PKT - 1); + }); + + it('getRXPacketSize aligns and caps correctly', () => { + const samples = 1000; + const size = inst.getRXPacketSize(samples); + // should be multiple of PACKET_ALIGN plus EXTRA_RX_DATA + expect((size - WebUsbXsdr.EXTRA_RX_DATA) % WebUsbXsdr.PACKET_ALIGN).toBe(0); + // very large samples should cap at MAX_PACKET_SIZE + EXTRA_RX_DATA + const hugeSamples = 1e7; + const capped = inst.getRXPacketSize(hugeSamples); + expect(capped).toBeLessThanOrEqual(WebUsbXsdr.MAX_PACKET_SIZE + WebUsbXsdr.EXTRA_RX_DATA); + }); + + it('encodeTxData produces buffer with headers for chunks and uses WebUsb.fillData', async () => { + const samplesCnt = 1024; + const byteLen = samplesCnt * COMPLEX_INT16_SIZE; + const dataBuf = new ArrayBuffer(byteLen); + const txBuf = { + datatype: DataType.ci16, + data: dataBuf, + byteLength: dataBuf.byteLength, + byteOffset: 0, + timestamp: BigInt(555), + discard_timestamp: false, + } as any; + + const spyFill = vi.spyOn(WebUsb as any, 'fillData').mockImplementation(() => { /* no-op */ }); + + const out = await inst.encodeTxData(txBuf, { sliceSamples: 512 }); + // should contain at least two headers (two chunks) + expect(out.byteLength).toBeGreaterThanOrEqual(inst.getTXPacketSize(512) * 2); + expect(spyFill).toHaveBeenCalled(); + + spyFill.mockRestore(); + }); + + it('decodeRxData decodes crafted buffer and returns RXBuffer', async () => { + const samples = 256; + const payloadSize = samples * COMPLEX_INT16_SIZE; + const trailerSize = WebUsbXsdr.TRAILER_SIZE; + const total = payloadSize + trailerSize; + const ab = new ArrayBuffer(total); + const dv = new DataView(ab); + // place timestamp at dataSize offset (little-endian) + const ts = BigInt(0x1234); + dv.setBigUint64(payloadSize, ts, true); + const viewPayload = new Uint8Array(ab, 0, payloadSize); + for (let i = 0; i < viewPayload.length; i++) viewPayload[i] = i & 0xff; + + const res = await inst.decodeRxData(new DataView(ab), samples, { datatype: DataType.ci16, id: 77 }); + expect(res).toHaveProperty('data'); + expect(res.samples).toBe(samples); + expect(res.id).toBe(77); + expect(res.recvsize).toBe(total); + }); +}); \ No newline at end of file diff --git a/packages/frontend-core/src/webusb/controlWebUsb.ts b/packages/frontend-core/src/webusb/controlWebUsb.ts new file mode 100644 index 0000000..3f4f0f0 --- /dev/null +++ b/packages/frontend-core/src/webusb/controlWebUsb.ts @@ -0,0 +1,576 @@ +import { DataType, CHUNK_SIZE } from '@websdr/core/common'; +import { Buffer } from 'buffer'; +import { sleep } from '@websdr/core/utils'; +import { NngWebSocket, Protocol } from '@/common/nngWebSocket'; +import { + getWebUsbManagerInstance, WebUsbManager, WebUsbManagerMode +} from './webUsbManager'; +import { WebUsbEndpoints } from './webUsbBase' +import type { StreamStatus } from './webUsbBase' + +const debug_control = false; + +export interface WebUsbDeviceInfo { + device: string, + deviceId: string, + deviceRev: string, + firmwareRev: string, +} + +export type RequestKeys = keyof typeof ControlWebUsb.Requests; + +interface DeferredParameter { + timer: ReturnType | undefined; + lastTime: number; +} + +export enum WebUsbChannels { + CHAN1 = 1 << 0, + CHAN2 = 1 << 1, + ALL_CHANS = CHAN1 | CHAN2, +} + +export enum WebUsbDirection { + RX = 1 << 0, + TX = 1 << 1, + RX_TX = RX | TX, +} + +export class ControlWebUsb extends EventTarget { + static FIRMWARE_GOLD = 1337; + static FLASH_SIZE = 0x1c0000; + static FLASH_SECTOR_SIZE = 256; + static HEADER_SIZE = 16; + static TRAILER_SIZE = 8; + static TRAILER_EXTRA_SIZE = 8; + static MIN_SAMPLES_IN_PKT = 128; + static MAX_SAMPLES_IN_PKT = 8192; + static SET_PARAMETER_TIMEOUT = 250; + + static Requests = { + //RX + SET_RX_FREQUENCY: { + req_method: 'sdr_set_rx_frequency', + req_params: { + chans: WebUsbChannels.CHAN1, + frequency: 100e6, + }, + }, + SET_RX_BANDWIDTH: { + req_method: 'sdr_set_rx_bandwidth', + req_params: { + chans: WebUsbChannels.CHAN1, + frequency: 1e6, + }, + }, + SET_RX_GAIN: { + req_method: 'sdr_set_rx_gain', + req_params: { + chans: WebUsbChannels.CHAN1, + gain: 15, + }, + }, + GET_RX_STREAM_STATUS: { + req_method: 'sdr_get_rx_stream_stats', + }, + //TX + SET_TX_FREQUENCY: { + req_method: 'sdr_set_tx_frequency', + req_params: { + chans: WebUsbChannels.CHAN1, + frequency: 100e6, + }, + }, + SET_TX_BANDWIDTH: { + req_method: 'sdr_set_tx_bandwidth', + req_params: { + chans: WebUsbChannels.CHAN1, + frequency: 1e6, + }, + }, + SET_TX_GAIN: { + req_method: 'sdr_set_tx_gain', + req_params: { + chans: WebUsbChannels.ALL_CHANS, + gain: 0, + }, + }, + //Common + CONNECT: { + req_method: 'sdr_connect', + req_params: { + connection_string: '*', + } + }, + DISCONNECT: { + req_method: 'sdr_disconnect', + }, + START_STREAMING: { + req_method: 'sdr_init_streaming', + req_params: { + chans: WebUsbChannels.CHAN1, + samplerate: 1e6, + packetsize: CHUNK_SIZE, + throttleon: 10e6, + param: 0, + mode: WebUsbDirection.RX_TX, + dataformat: DataType.ci16, + // stream_type: ControlStreamType.raw, //right now just for behemoth daemon + }, + }, + STOP_STREAMING: { + req_method: 'sdr_stop_streaming', + }, + CONTROL_STREAMING: { + req_method: 'sdr_ctrl_streaming', + req_params: { + samplerate: 1e6, + throttleon: 10e6, + param: 42, + }, + }, + GET_SENSOR: { + req_method: 'sdr_get_sensor', + req_params: { + sensor: 'sdr_temp', + }, + }, + SET_PARAMETER: { + req_method: 'sdr_set_parameter', + req_params: { + path: '', + value: 0, + }, + }, + GET_PARAMETER: { + req_method: 'sdr_get_parameter', + req_params: { + path: '', + }, + }, + DEBUG_DUMP: { + req_method: 'sdr_debug_dump', + }, + CALIBRATE: { + req_method: 'sdr_calibrate', + req_params: { + param: 0, + chans: 0, + } + }, + //Flash + FLASH_READ: { + req_method: 'flash_read', + req_params: { + offset: 0, + param: 0, + }, + }, + FLASH_WRITE: { + req_method: 'flash_write_sector', + req_data: '', + req_params: { + offset: 0, + checksum: 0, + param: 0, + }, + }, + FLASH_ERASE: { + req_method: 'flash_erase', + req_params: { + offset: 0, + length: 0, + }, + }, + //Information + GET_FIRMWARE_REVISION: { + req_method: 'sdr_get_revision', + }, + DISCOVER: { + req_method: 'sdr_discover' + }, + }; + + private fd: number = -1; + protected control_ep: number; + protected control_rep_ep: number; + protected notification_ep: number; + protected type: DataType; + protected commands: Record = {}; + protected parameters: Record = {}; + protected _start_ms: number = 0; + protected _end_ms: number = 0; + protected _changeParmTimers = new Map(); + protected _onControlOpen: EventListenerOrEventListenerObject; + protected _onDebugWSMessage: EventListenerOrEventListenerObject; + protected _onDebugWSOpen: EventListenerOrEventListenerObject; + protected _onDebugWSClose: EventListenerOrEventListenerObject; + // protected _fillData: (outbuf: ArrayBuffer, iqbuf: ArrayBuffer) => void; + // protected _getSamplesCnt: (iqbuf: ArrayBuffer) => number; + protected _debugServer?: string; + protected _debugWS?: NngWebSocket; + protected _mode: WebUsbManagerMode; + protected _webUsbManager?: WebUsbManager; + + constructor(params: ControlWebUsbParams = ControlWebUsbInitialParams) { + super() + this.control_ep = params.control_ep !== undefined ? params.control_ep : ControlWebUsbInitialParams.control_ep!; + this.control_rep_ep = params.control_rep_ep !== undefined ? params.control_rep_ep : ControlWebUsbInitialParams.control_rep_ep!; + this.notification_ep = params.notification_ep !== undefined ? params.notification_ep : ControlWebUsbInitialParams.notification_ep!; + this.type = params.type !== undefined ? params.type : ControlWebUsbInitialParams.type!; + this._debugServer = params.debugServer !== undefined ? params.debugServer : ControlWebUsbInitialParams.debugServer!; + this._mode = params.mode !== undefined ? params.mode : ControlWebUsbInitialParams.mode!; + if (this._mode !== WebUsbManagerMode.UNKNOWN) + this._webUsbManager = getWebUsbManagerInstance(this._mode); + this._onControlOpen = this.onControlOpen.bind(this) + this._onDebugWSMessage = this.onDebugWSMessage.bind(this) + this._onDebugWSOpen = this.onDebugWSOpen.bind(this) + this._onDebugWSClose = this.onDebugWSClose.bind(this) + this.addEventListener('open', this._onControlOpen) + } + + setCustomWebUsbManager(webUsbManager: WebUsbManager | undefined) { + this._webUsbManager = webUsbManager; + } + + getRequest(req: Record, args = {}, ext_args = {}) { + let ret = req + // ret.id = '12345'; + if (ret.req_params) + Object.assign(ret.req_params, args) + Object.assign(ret, ext_args) + return ret + } + + getRequestBuffer(req: Record, args = {}, ext_args = {}) { + return JSON.stringify(this.getRequest(req, args, ext_args)) + } + + async setParameterNow(parm: RequestKeys, args: (() => Record) | Record) { + // console.log('SEND COMMAND AT', Date.now()); + const a = typeof args === 'function' ? args() : args; + await this.sendCommand(parm as RequestKeys, a); + } + + async setParameter(parm: RequestKeys, args: (() => Record) | Record, now = false) { + if (globalThis.debug_mode || debug_control) + console.log('ControlWebUsb.setParameter(', parm, ', ', args, '): ') + if (now) { + await this.setParameterNow(parm, args); + } else { + if (!this._changeParmTimers.has(parm)) + this._changeParmTimers.set(parm, { timer: undefined, lastTime: 0 }); + + const defferedParm: DeferredParameter = this._changeParmTimers.get(parm)!; + + // if (defferedParm.timer !== undefined) { + // clearTimeout(defferedParm.timer); + // defferedParm.timer = undefined; + // } + const curtime = Date.now(); + if (curtime - defferedParm.lastTime > ControlWebUsb.SET_PARAMETER_TIMEOUT) { + defferedParm.lastTime = curtime; + this._changeParmTimers.set(parm, defferedParm); + await this.setParameterNow(parm, args); + } else if (defferedParm.timer === undefined) { + defferedParm.timer = setTimeout(async () => { + const defferedParm = this._changeParmTimers.get(parm); + if (defferedParm !== undefined) { + defferedParm.timer = undefined; + defferedParm.lastTime = Date.now(); + this._changeParmTimers.set(parm, defferedParm); + } + await this.setParameterNow(parm, args); + }, ControlWebUsb.SET_PARAMETER_TIMEOUT) + } + } + } + + async setSdrParameter(path: string, value: string | bigint | number | undefined): Promise { + if (globalThis.debug_mode || debug_control) + console.log('ControlWebUsb.setSdrParameter(', path, ', ', value, '): ') + const data = await this.sendCommand('SET_PARAMETER', { path, value }); + if (!data || data.result !== 0) { + throw new Error(`ControlWebUsb.setSdrParameter: Error setting parameter ${path}`); + } + } + + async getSdrParameter(path: string): Promise { + if (globalThis.debug_mode || debug_control) + console.log('ControlWebUsb.getSdrParameter(', path, '): ') + const data = await this.sendCommand('GET_PARAMETER', { path }); + if (data && data.result === 0 && data.details?.path === path) { + return data.details.value; + } else { + throw new Error(`ControlWebUsb.getSdrParameter: Error getting parameter ${path}`); + } + // return undefined; //??? + } + + async sendRawCommand(req: Record): Promise> { + return this._webUsbManager ? await this._webUsbManager.sendCommand(this.fd, req) : {}; + } + + async sendCommand(cmd: RequestKeys, args = {}, ext_args = {}) { + if (this.fd < 0) { + throw new Error(`ControlWebUsb::sendCommand(${cmd}): Error this.fd = ${this.fd}`) + } + try { + // Ensure we have a known request template for this command + const template = ControlWebUsb.Requests[cmd] + if (!template) { + throw new Error(`ControlWebUsb.sendCommand: Unknown command "${String(cmd)}"`) + } + if (cmd === 'START_STREAMING' || cmd === 'STOP_STREAMING' || (cmd === 'CONTROL_STREAMING' && (args as any).param === 0)) { + // console.log('START/STOP COMMAND DETECTED', cmd, 'streamSatatus', await this.getStreamStatus()) + while (await this.getStreamStatus() === 'PENDING') await sleep(0.01); + const streamStatus = await this.getStreamStatus(); + if (streamStatus === 'STARTED' && cmd === 'CONTROL_STREAMING' && (args as any).param === 0 || + streamStatus === 'PREPARED' && cmd === 'START_STREAMING' || + streamStatus === 'STOPPED' && cmd === 'STOP_STREAMING') { + // console.log('ALREADY STARTED/STOPPED, streamStatus', streamStatus, 'cmd', cmd) + return {}; + } + await this.setStreamStatus('PENDING'); + } + const req = this.getRequest(ControlWebUsb.Requests[cmd], args, ext_args); + // console.log('REQUEST', req); + if (globalThis.debug_mode || debug_control) { + this._start_ms = Date.now(); + console.log('1. ControlWebUsb.sendCommand(', cmd, ', ', args, ', ', ext_args, '): req =', req); + } + const data = await this.sendRawCommand(req); + // console.log('DATA', data); + if (globalThis.debug_mode || debug_control) { + this._end_ms = Date.now(); + console.log('2. ControlWebUsb.sendCommand(', cmd, ', ', args, ', ', ext_args, '): data =', data, 'duration', this._end_ms - this._start_ms); + } + if (cmd === 'START_STREAMING' || cmd === 'STOP_STREAMING' || cmd === 'CONTROL_STREAMING' && (args as any).param === 0) { + if (data['error'] === undefined) { + if (cmd === 'START_STREAMING') this.setStreamStatus('PREPARED'); + else if (cmd === 'STOP_STREAMING') this.setStreamStatus('STOPPED'); + else if (cmd === 'CONTROL_STREAMING' && (args as any).param === 0) this.setStreamStatus('STARTED'); + } else this.setStreamStatus('INVALID'); + } + + return data; + } catch (err) { + console.error('ControlWebUsb.sendCommand:', err) + throw err; + } + } + + init() { + // console.log('init()') + try { + this.dispatchEvent(new Event('init')); + } catch (err) { + console.error('init:', err) + } + } + + async open(fd: number) { + // console.log('open(', dev, ')') + try { + this.fd = fd; + this.dispatchEvent(new Event('open')); + if (this._debugServer !== undefined && this._debugServer !== '') { + console.warn(`Debug WS server at ${this._debugServer}`) + this._debugWS = new NngWebSocket({ url: this._debugServer, binaryType: NngWebSocket.TEXT, protocol: Protocol.UNKNOWN }); + this._debugWS.addEventListener('open', this._onDebugWSOpen) + this._debugWS.addEventListener('close', this._onDebugWSClose) + this._debugWS.addEventListener('message', this._onDebugWSMessage) + await this._debugWS.open(); + } + } catch (err) { + console.error('open:', err) + } + } + + isOpen() { + return this.fd >= 0; + } + + async close() { + this.dispatchEvent(new Event('close')); + + // clear pending parameter timers to avoid leaks + for (const [_, t] of this._changeParmTimers) { + if (t.timer !== undefined) { + clearTimeout(t.timer as any); + t.timer = undefined; + } + } + this._changeParmTimers.clear(); + + if (this._debugWS !== undefined) { + try { + // ensure debug WS is closed before removing listeners + await this._debugWS.close(); + } catch (err) { + console.warn('ControlWebUsb.close: debugWS.close() failed', err); + } + this._debugWS.removeEventListener('open', this._onDebugWSOpen) + this._debugWS.removeEventListener('close', this._onDebugWSClose) + this._debugWS.removeEventListener('message', this._onDebugWSMessage) + this._debugWS = undefined; + } + + // reset fd so isOpen() reflects closed state + this.fd = -1; + } + + onControlOpen() { + this.init(); + } + + async flashReadSector(buf: Uint8Array, offs: number, gold: boolean = false): Promise { + const data = await this.sendCommand('FLASH_READ', { offset: offs, param: gold ? ControlWebUsb.FIRMWARE_GOLD : 0 }); + if (data && data.details && data.details.data) { + const encBuf = Buffer.from(data.details.data, 'base64'); + buf.set(encBuf); + } else { + throw new Error('ControlWebUsb.flashRead: Error reading flash'); + } + } + + async flashRead(gold: boolean = false): Promise { + const buf = new ArrayBuffer(ControlWebUsb.FLASH_SIZE); + const view = new Uint8Array(buf); + const sectorCnt = ControlWebUsb.FLASH_SIZE / ControlWebUsb.FLASH_SECTOR_SIZE; + + try { + for (let i = 0; i < sectorCnt; ++i) { + this.dispatchEvent(new CustomEvent('progress', { detail: { message: 'Flash memory reading', value: i, max: sectorCnt } })) + const offs = i * ControlWebUsb.FLASH_SECTOR_SIZE; + await this.flashReadSector(view.subarray(offs, offs + ControlWebUsb.FLASH_SECTOR_SIZE), offs, gold); + } + } catch (err) { + console.error('ControlWebUsb.flashRead: Error: ', err) + } + + return buf; + } + + async flashWriteSector(buf: Uint8Array, offs: number, gold: boolean = false): Promise { + let cksum = 0; + buf.forEach((val) => cksum += val); + const encBuf = Buffer.from(buf); + const data = encBuf.toString('base64'); + await this.sendCommand('FLASH_WRITE', { offset: offs, checksum: cksum, param: gold ? ControlWebUsb.FIRMWARE_GOLD : 0 }, { req_data: data }); + } + + async flashErase(offs: number, len: number) { + await this.sendCommand('FLASH_ERASE', { offset: offs, length: len }); + } + + async flashWrite(flash: ArrayBuffer, gold: boolean = false): Promise { + if (flash.byteLength > ControlWebUsb.FLASH_SIZE) { + const errstr = `ControlWebUsb.flashWrite: Error: size of flash image (${flash.byteLength} bytes) bigger than flash size (${ControlWebUsb.FLASH_SIZE} bytes)`; + console.error(errstr); + throw new Error(errstr); + } + + this.dispatchEvent(new CustomEvent('progress', { detail: { message: 'Flash memory erasing', value: 50, max: 100 } })) + const writeLen = (flash.byteLength + 4095) & 0xfffff000; + await this.flashErase(0, writeLen); + + const sectorBuf = new ArrayBuffer(ControlWebUsb.FLASH_SECTOR_SIZE); + const viewSectorBuf = new Uint8Array(sectorBuf); + const viewFlash = new Uint8Array(flash); + // const sectorCnt = ControlWebUsb.FLASH_SIZE / ControlWebUsb.FLASH_SECTOR_SIZE; + const flashSectorCnt = (writeLen / ControlWebUsb.FLASH_SECTOR_SIZE) >> 0; + + try { + for (let i = 0; i < flashSectorCnt; ++i) { + this.dispatchEvent(new CustomEvent('progress', { detail: { message: 'Flash memory writing', value: i, max: flashSectorCnt } })) + const offs = i * ControlWebUsb.FLASH_SECTOR_SIZE; + const buf = viewFlash.subarray(offs, offs + ControlWebUsb.FLASH_SECTOR_SIZE); + let writeBuf = buf; + if (buf.length < ControlWebUsb.FLASH_SECTOR_SIZE) { + viewSectorBuf.set(viewFlash.subarray(offs, offs + ControlWebUsb.FLASH_SECTOR_SIZE)); + viewSectorBuf.fill(0xff, buf.length); + writeBuf = viewSectorBuf; + } + await this.flashWriteSector(writeBuf, offs, gold); + } + // viewSectorBuf.fill(0xff); + // for (let i = flashSectorCnt; i < sectorCnt; ++i) { + // const offs = i * ControlWebUsb.FLASH_SECTOR_SIZE; + // await this.flashWriteSector(viewSectorBuf, offs); + // } + } catch (err) { + console.error('ControlWebUsb.flashWrite: Error: ', err) + throw err; + } + } + + /** + * Get device info. + * @param strict if true, throw on error; otherwise return empty/default info and log a warning + */ + async getDeviceInfo(strict = false): Promise { + const res = await this.sendCommand('GET_FIRMWARE_REVISION'); + // console.log('GET_FIRMWARE_REVISION', res) + // return { device: res?.device, deviceId: res?.devid, deviceRev: res?.devrev, firmwareRev: res?.revision }; + if (res && res.result === 0) { + return { device: res.details.device, deviceId: res.details.devid, deviceRev: res.details.devrev, firmwareRev: res.details.revision }; + } + const msg = 'ControlWebUsb.getDeviceInfo: Error retrieving firmware revision'; + if (strict) throw new Error(msg); + console.warn(msg, res); + return { device: '', deviceId: '', deviceRev: '', firmwareRev: '' }; + } + + async getStreamStatus(): Promise { + if (this.fd < 0 || !this._webUsbManager) return 'INVALID' + return await this._webUsbManager.getStreamStatus(this.fd); + } + + async setStreamStatus(status: StreamStatus) { + if (this.fd < 0 || !this._webUsbManager) return; + await this._webUsbManager.setStreamStatus(this.fd, status); + } + + async onDebugWSMessage(event: Event) { + if (this._webUsbManager) { + const data = (event as MessageEvent).data; + console.warn('Received debug command:', data); + const res = await this._webUsbManager.sendDebugCommand(this.fd, data); + console.warn('Reply to debug command:', res); + await this._debugWS?.send(res); + } + } + + onDebugWSOpen(event: Event) { + console.log('ControlWebUsb: connected to url', this._debugServer); + } + + onDebugWSClose(event: Event) { + console.log('ControlWebUsb: disconnected from url', this._debugServer); + } + + async calibrate(mode: 'tx' | 'rx' | 'trx') { + const tx = mode === 'tx' || mode === 'trx' ? 10 : 0; + const rx = mode === 'rx' || mode === 'trx' ? 5 : 0; + await this.sendCommand('CALIBRATE', { param: tx + rx }); + } +} + +export interface ControlWebUsbParams { + control_ep?: number, + control_rep_ep?: number, + notification_ep?: number, + type?: DataType, + debugServer?: string, + mode?: WebUsbManagerMode; +} + +export const ControlWebUsbInitialParams: ControlWebUsbParams = { + control_ep: WebUsbEndpoints.CONTROL_EP, + control_rep_ep: WebUsbEndpoints.CONTROL_EP, + notification_ep: WebUsbEndpoints.NOTIFY_EP, + type: DataType.cf32, + debugServer: '', + mode: WebUsbManagerMode.WORKER, +} \ No newline at end of file diff --git a/packages/frontend-core/src/webusb/ensureWebUsb.ts b/packages/frontend-core/src/webusb/ensureWebUsb.ts new file mode 100644 index 0000000..1c9e211 --- /dev/null +++ b/packages/frontend-core/src/webusb/ensureWebUsb.ts @@ -0,0 +1,52 @@ +export async function ensureWebUsb(): Promise { + if (typeof navigator !== 'undefined' && (navigator as any).usb) { + return (navigator as any).usb; + } + + // Try prefer "usb" package (recommended by maintainer warning), then fallback to "webusb" + const tryLoad = async (name: string) => { + try { + const mod = await import(name); + if (!mod) { + throw null; + } + // try common export shapes + const webusb = mod.webusb ?? mod.default ?? mod; + return webusb; + } catch { + try { + // CommonJS fallback + // eslint-disable-next-line @typescript-eslint/no-var-requires + const mod = require(name); + if (!mod) { + throw null; + } + const webusb = mod.webusb ?? mod.default ?? mod; + return webusb; + } catch { + return null; + } + } + }; + + let usbImpl = await tryLoad('usb'); + if (!usbImpl) { + usbImpl = await tryLoad('webusb'); + } + if (!usbImpl) { + throw new Error('No WebUSB implementation found: install "usb" (preferred) or "webusb"'); + } + + // Patch globals so consumer code expecting navigator.usb works + try { + if (typeof navigator === 'undefined') { + (globalThis as any).navigator = { usb: usbImpl }; + } else { + (navigator as any).usb = usbImpl; + } + } catch { + // best-effort only + } + + return usbImpl; +} diff --git a/packages/frontend-core/src/webusb/index.ts b/packages/frontend-core/src/webusb/index.ts new file mode 100644 index 0000000..a1f97d0 --- /dev/null +++ b/packages/frontend-core/src/webusb/index.ts @@ -0,0 +1,22 @@ +// Re-exporting all types from WebUsb module +export { + WebUsbChannels, WebUsbDirection, ControlWebUsb, ControlWebUsbInitialParams +} from './controlWebUsb'; +export type { WebUsbDeviceInfo, RequestKeys, ControlWebUsbParams } from './controlWebUsb'; +export { SDRDevicesIds, getDeviceHash, getWebUsbInstance } from './webUsb'; +export type { DeviceId } from './webUsb'; +export { + WebUsbEndpoints, DeviceStreamType, DeviceStreamTypeNames, DeviceDataType, + DeviceDataTypeNames, DefaultDeviceConfiguration, WebUsb +} from './webUsbBase'; +export type { + StreamStatus, RXBuffer, RXDecoderOptions, TXBuffer, TXEncoderOptions, CommandRequest, + DataRequest, DeviceParamRange, DeviceStreamParameters, DeviceStreamTypes, DeviceDataTypes, + DeviceConfiguration, WebUsbParams +} from './webUsbBase'; +export { write_ep1, write_ep2, read_ep1, read_ep2, write_log_js } from './webUsbControlApi'; +export { initControl, isControlInitialized, WebUsbDeviceManager } from './webUsbDeviceManager'; +export type { WebUsbManagerParams } from './webUsbDeviceManager'; +export { WebUsbManagerMode, getWebUsbManagerInstance, WebUsbManager } from './webUsbManager'; +export type { RequestDeviceInfo } from './webUsbManager'; +export { ensureWebUsb } from './ensureWebUsb'; diff --git a/packages/frontend-core/src/webusb/webUsb.ts b/packages/frontend-core/src/webusb/webUsb.ts new file mode 100644 index 0000000..ee61ec5 --- /dev/null +++ b/packages/frontend-core/src/webusb/webUsb.ts @@ -0,0 +1,41 @@ +import type { WebUsbParams } from './webUsbBase'; +import { WebUsbXsdr, WebUsbUsdr } from './webUsbWsdr'; +import { WebUsbLimeSdr } from './webUsbLimeSdr'; + +export interface DeviceId { + vendorId: number, + productId: number, +} + +export const SDRDevicesIds: Array = [ + { vendorId: 0xfaee, productId: 0xdea9 }, //Wavelet WebSDR (LMS6002) mock ids + { vendorId: 0xfaef, productId: 0xdea9 }, //Wavelet WebSDR (LMS7002) mock ids + { vendorId: 0x3727, productId: 0x1001 }, //Wavelet WebSDR (LMS6002) real ids + { vendorId: 0x3727, productId: 0x1011 }, //Wavelet WebSDR (LMS7002) real ids + { vendorId: 0x0403, productId: 0x601f }, //Future Technology Devices International, Ltd LimeSDR Mini (LMS7002) +]; + +export function getDeviceHash(devId: DeviceId | undefined): number { + if (!devId) return 0; + // ensure 32-bit unsigned hash to avoid negative values because of JS bitshift sign + const v = (devId.vendorId & 0xffff) >>> 0; + const p = (devId.productId & 0xffff) >>> 0; + return ((v << 16) | p) >>> 0; +} + +// use Map for clearer typing and safer lookups +type WebUsbConstructor = new (params: WebUsbParams) => any; + +const webUsbMap = new Map([ + [getDeviceHash(SDRDevicesIds[0]), WebUsbUsdr], + [getDeviceHash(SDRDevicesIds[1]), WebUsbXsdr], + [getDeviceHash(SDRDevicesIds[2]), WebUsbUsdr], + [getDeviceHash(SDRDevicesIds[3]), WebUsbXsdr], + [getDeviceHash(SDRDevicesIds[4]), WebUsbLimeSdr], +]); + +export function getWebUsbInstance(key: number, params: WebUsbParams): InstanceType | undefined { + const webUsbConstructor = webUsbMap.get(key); + if (!webUsbConstructor) return undefined; + return new webUsbConstructor(params); +} diff --git a/packages/frontend-core/src/webusb/webUsb.worker.ts b/packages/frontend-core/src/webusb/webUsb.worker.ts new file mode 100644 index 0000000..d137cbf --- /dev/null +++ b/packages/frontend-core/src/webusb/webUsb.worker.ts @@ -0,0 +1,188 @@ +import { initControl, isControlInitialized } from './webUsbDeviceManager'; +import { WebUsb } from './webUsbBase'; +import type { + WebUsbWorkerRequest, + WebUsbWorkerResponse, + WebUsbRequestType, +} from './webUsb.worker.types'; + +// hoist reusable sets to module scope to avoid recreating them per message +const NON_MANAGER_TYPES: ReadonlySet = new Set([ + 'START', 'STOP', 'GET_OPENED_DEVICE_LIST', 'CLOSE_ALL' +]); + + +const NOT_NEEDED_DEVICE_TYPES: ReadonlySet = new Set([ + 'OPEN', 'CLOSE', 'GET_OPENED_DEVICE_LIST' +]); + +// helper to ensure correct typing of sendResponse calls +const sendResponse = (type: WebUsbRequestType | undefined, id: number | string | undefined, res: 'ok' | 'error', payload?: any, err?: any) => { + const msg: WebUsbWorkerResponse = { type, id, res, ret: payload, err }; + try { + self.postMessage(msg); + } catch (e) { + console.error('postMessage failed', e); + } +}; + +// main handler — accept unknown and narrow to discriminated union +self.onmessage = async (event: MessageEvent) => { + const msg = event.data as WebUsbWorkerRequest | undefined; + + // basic runtime validation + if (!msg || typeof (msg as any).type !== 'string') { + sendResponse(undefined, (msg as any)?.id, 'error', undefined, 'invalid message'); + return; + } + + try { + if (globalThis.debug_mode) console.log('Message from WebUsb', msg); + let dev: WebUsb | undefined = undefined; + let ret: any; + + // get device manager or device instance as needed + if (!NON_MANAGER_TYPES.has(msg.type)) { + if (globalThis.webUsbDeviceManager === undefined) { + sendResponse(msg.type, msg.id, 'error', undefined, 'webUsbDeviceManager is not defined'); + return; + } + if (!NOT_NEEDED_DEVICE_TYPES.has(msg.type)) { + // safe access because CLOSE and OPEN handled separately + dev = globalThis.webUsbDeviceManager.getDevice((msg as any).fd); + if (!dev) { + sendResponse(msg.type, msg.id, 'error', undefined, `error getting a webusb device (fd = ${(msg as any).fd})`); + return; + } + } + } + + switch (msg.type) { + case 'START': + await initControl(); + sendResponse(msg.type, msg.id, isControlInitialized() ? 'ok' : 'error'); + break; + + case 'STOP': + sendResponse(msg.type, msg.id, isControlInitialized() ? 'ok' : 'error'); + break; + + case 'OPEN': { + const m = msg; // narrowed to OPEN + await initControl(); + dev = await globalThis.webUsbDeviceManager!.open(m.vendorId, m.productId); + ret = dev !== undefined ? dev.fd : -1; + sendResponse(msg.type, msg.id, ret >= 0 ? 'ok' : 'error', ret, + ret < 0 ? `error opening a webusb device (vid = 0x${m.vendorId.toString(16)}, pid = 0x${m.productId.toString(16)})` : undefined); + break; + } + + case 'CLOSE': { + const m = msg; + ret = await globalThis.webUsbDeviceManager!.close(m.fd); + sendResponse(msg.type, msg.id, ret ? 'ok' : 'error', ret, !ret ? `error closing a webusb device (fd = ${m.fd})` : undefined); + break; + } + + case 'CLOSE_ALL': + await globalThis.webUsbDeviceManager?.closeAll(); + sendResponse(msg.type, msg.id, 'ok', true); + break; + + case 'GET_DEV_NAME': { + const m = msg; + ret = dev!.getName(); + sendResponse(msg.type, msg.id, ret ? 'ok' : 'error', ret, !ret ? `error getting a name of webusb device (fd = ${m.fd})` : undefined); + break; + } + + case 'GET_SERIAL_NUMBER': { + const m = msg; + ret = dev!.getSerialNumber(); + sendResponse(msg.type, msg.id, ret ? 'ok' : 'error', ret, !ret ? `error getting a serial number of webusb device (fd = ${m.fd})` : undefined); + break; + } + + case 'GET_RX_SAMPLES_COUNT': { + const m = msg as any; + ret = dev!.getRXSamplesCount(m.samples); + sendResponse(msg.type, msg.id, ret ? 'ok' : 'error', ret, !ret ? `error getting samples count of webusb device (fd = ${m.fd})` : undefined); + break; + } + + case 'SEND_COMMAND': { + const m = msg; + dev!.sendCommand(m.req) + .then(res => sendResponse(msg.type, msg.id, 'ok', res)) + .catch(err => sendResponse(msg.type, msg.id, 'error', undefined, String(err))); + break; + } + + case 'SEND_DEBUG_COMMAND': { + const m = msg; + dev!.sendDebugCommand(m.req) + .then(res => sendResponse(msg.type, msg.id, 'ok', res)) + .catch(err => sendResponse(msg.type, msg.id, 'error', undefined, String(err))); + break; + } + + case 'SUBMIT_RX_PACKET': { + const m = msg; + dev!.submitRxPacket(m.samples, m.opts) + .then((res) => sendResponse(msg.type, msg.id, 'ok', res)) + .catch(err => sendResponse(msg.type, msg.id, 'error', undefined, String(err))); + break; + } + + case 'SEND_TX_PACKET': { + const m = msg; + dev!.sendTxPacket(m.data, m.opts) + .then((res) => sendResponse(msg.type, msg.id, 'ok', { bytesWritten: res.usbOutTransferResult?.bytesWritten })) + .catch(err => sendResponse(msg.type, msg.id, 'error', undefined, String(err))); + break; + } + + case 'GET_STREAM_STATUS': { + const m = msg; + ret = dev!.getStreamStatus(); + sendResponse(msg.type, msg.id, ret ? 'ok' : 'error', ret, !ret ? `error getting a stream status of webusb device (fd = ${m.fd})` : undefined); + break; + } + + case 'SET_STREAM_STATUS': { + const m = msg; + dev!.setStreamStatus(m.status); + sendResponse(msg.type, msg.id, 'ok', m.status); + break; + } + + case 'GET_CONFIGURATION': { + const m = msg; + ret = dev!.getConfiguration(); + sendResponse(msg.type, msg.id, ret ? 'ok' : 'error', ret, !ret ? `error getting a configuration of webusb device (fd = ${m.fd})` : undefined); + break; + } + + case 'GET_OPENED_DEVICE_LIST': { + const devs: any[] = []; + if (globalThis.webUsbDeviceManager !== undefined) { + const dev_fds = globalThis.webUsbDeviceManager.getDevices(); + if (dev_fds !== undefined) { + dev_fds.forEach((fd: number) => { + const d = globalThis.webUsbDeviceManager!.getDevice(fd); + if (d) devs.push({ devName: WebUsb.getDeviceName(d.device), vendorId: d.vid, productId: d.pid }) + }) + } + } + sendResponse(msg.type, msg.id, 'ok', devs); + break; + } + + default: + throw new Error('Unhandled message type: ' + msg as never); + } + } catch (e) { + console.error('webUsb.worker handler failed', e); + try { self.postMessage({ type: (msg as any)?.type, id: (msg as any)?.id, res: 'error', err: String(e) }); } catch { /* ignore */ } + } +} \ No newline at end of file diff --git a/packages/frontend-core/src/webusb/webUsb.worker.types.ts b/packages/frontend-core/src/webusb/webUsb.worker.types.ts new file mode 100644 index 0000000..b052beb --- /dev/null +++ b/packages/frontend-core/src/webusb/webUsb.worker.types.ts @@ -0,0 +1,77 @@ +/** + * Typed message interfaces for the worker channel. + * Keep them permissive (optional fields) but provide names for common fields + * so TypeScript can catch typos and missing properties earlier. + */ + +export type WebUsbRequestType = + | 'START' | 'STOP' | 'OPEN' | 'CLOSE' | 'CLOSE_ALL' + | 'GET_DEV_NAME' | 'GET_SERIAL_NUMBER' | 'GET_RX_SAMPLES_COUNT' + | 'SEND_COMMAND' | 'SEND_DEBUG_COMMAND' | 'SUBMIT_RX_PACKET' | 'SEND_TX_PACKET' + | 'GET_STREAM_STATUS' | 'SET_STREAM_STATUS' | 'GET_CONFIGURATION' | 'GET_OPENED_DEVICE_LIST'; + +interface IBaseReq { + id?: number | string; +} + +/* Discriminated (tagged) union request types */ +export interface IStartReq extends IBaseReq { type: 'START' } +export interface IStopReq extends IBaseReq { type: 'STOP' } + +export interface IOpenReq extends IBaseReq { + type: 'OPEN'; + vendorId: number; + productId: number; +} + +export interface ICloseReq extends IBaseReq { + type: 'CLOSE'; + fd: number; +} + +export interface ICloseAllReq extends IBaseReq { type: 'CLOSE_ALL' } + +export interface IGetDevNameReq extends IBaseReq { type: 'GET_DEV_NAME'; fd: number } +export interface IGetSerialReq extends IBaseReq { type: 'GET_SERIAL_NUMBER'; fd: number } +export interface IGetRxSamplesCountReq extends IBaseReq { type: 'GET_RX_SAMPLES_COUNT'; fd: number; samples?: number } + +export interface ISendCommandReq extends IBaseReq { type: 'SEND_COMMAND'; fd: number; req: any } +export interface ISendDebugCommandReq extends IBaseReq { type: 'SEND_DEBUG_COMMAND'; fd: number; req: any } + +export interface ISubmitRxPacketReq extends IBaseReq { type: 'SUBMIT_RX_PACKET'; fd: number; samples: any; opts?: any } +export interface ISendTxPacketReq extends IBaseReq { type: 'SEND_TX_PACKET'; fd: number; data: any; opts?: any } + +export interface IGetStreamStatusReq extends IBaseReq { type: 'GET_STREAM_STATUS'; fd: number } +export interface ISetStreamStatusReq extends IBaseReq { type: 'SET_STREAM_STATUS'; fd: number; status: any } + +export interface IGetConfigurationReq extends IBaseReq { type: 'GET_CONFIGURATION'; fd: number } + +export interface IGetOpenedDeviceListReq extends IBaseReq { type: 'GET_OPENED_DEVICE_LIST' } + +/* union of all request shapes */ +export type WebUsbWorkerRequest = + | IStartReq + | IStopReq + | IOpenReq + | ICloseReq + | ICloseAllReq + | IGetDevNameReq + | IGetSerialReq + | IGetRxSamplesCountReq + | ISendCommandReq + | ISendDebugCommandReq + | ISubmitRxPacketReq + | ISendTxPacketReq + | IGetStreamStatusReq + | ISetStreamStatusReq + | IGetConfigurationReq + | IGetOpenedDeviceListReq; + +/* response shape */ +export interface WebUsbWorkerResponse { + type?: WebUsbRequestType | undefined; + id?: number | string | undefined; + res: 'ok' | 'error'; + ret?: any; + err?: any; +} \ No newline at end of file diff --git a/packages/frontend-core/src/webusb/webUsbBase.ts b/packages/frontend-core/src/webusb/webUsbBase.ts new file mode 100644 index 0000000..ebbf2ae --- /dev/null +++ b/packages/frontend-core/src/webusb/webUsbBase.ts @@ -0,0 +1,511 @@ +import type { ControlModule } from '@/control/control' +import { CircularBuffer } from '@websdr/core/utils'; +import { DataType } from '@websdr/core/common'; +import { CHUNK_SIZE, COMPLEX_FLOAT_SIZE, COMPLEX_INT16_SIZE } from '@websdr/core/common'; +import { bufferF32ToI16, bufferI16ToF32 } from '@websdr/core/transform'; +import { sleep } from '@websdr/core/utils'; + +const debug_webusb = false; + +export enum WebUsbEndpoints { + CONTROL_EP = 1, + NOTIFY_EP = 2, + STREAM_EP = 3, +} + +export type StreamStatus = 'INVALID' | 'STOPPED' | 'STARTED' | 'PREPARED' | 'PENDING'; + +export interface RXBuffer { + data: ArrayBufferLike, + datatype: DataType, + id: number, + samples: number, + timestamp: bigint, + overrun: number, + realigned: number, + dropped: number, + recvsize: number, +} + +export interface RXDecoderOptions { + extra_meta?: boolean, + datatype?: DataType, + data?: ArrayBufferLike, + id?: number, +} + +export interface TXBuffer { + data: ArrayBufferLike, + byteOffset?: number, + byteLength?: number, + datatype: DataType, + discard_timestamp: boolean, + timestamp: bigint, +} + +export interface TXResult { + usbOutTransferResult?: USBOutTransferResult, +} + +export interface TXEncoderOptions { + data?: ArrayBufferLike, + // id?: number, + allowDrop?: boolean, + sliceSamples?: number, +} + +export interface CommandRequest { + req: Record, + resolve?: (rep: Record) => void | undefined, + reject?: (errmsg: string) => void | undefined, +} + +export interface DataRequest { + pkt: ArrayBufferLike; + resolve?: (rep: Record) => void | undefined, + reject?: (errmsg: string) => void | undefined, +} + +export interface DeviceParamRange { + min: number, + max: number, +} + +export interface DeviceStreamParameters { + dataTypes: Array, +} + +export enum DeviceStreamType { + raw = 'raw', + sa = 'sa', + rtsa = 'rtsa', +} + +export const DeviceStreamTypeNames = { + [DeviceStreamType.raw]: 'raw', + [DeviceStreamType.sa]: 'sa', + [DeviceStreamType.rtsa]: 'rtsa', +} + +export type DeviceStreamTypes = { [key in DeviceStreamType]?: DeviceStreamParameters }; + +export enum DeviceDataType { + ci16 = 'ci16', + ci12 = 'ci12', +} + +export const DeviceDataTypeNames = { + [DeviceDataType.ci16]: 'ci16', + [DeviceDataType.ci12]: 'ci12', +} + +export type DeviceDataTypes = { [key in DeviceStreamType]?: DeviceStreamParameters }; + +export interface DeviceConfiguration { + defaultSamplesCount: number, + rxFrequencyRange: DeviceParamRange, + txFrequencyRange: DeviceParamRange, + bandwidthRange: DeviceParamRange, + rateRange: DeviceParamRange, + streamTypes: DeviceStreamTypes, + txRxDelay: number, + warmupPackets: number, +} + +export const DefaultDeviceConfiguration: DeviceConfiguration = { + defaultSamplesCount: CHUNK_SIZE, + rxFrequencyRange: { min: 30e6, max: 3800e6 }, + txFrequencyRange: { min: 30e6, max: 3800e6 }, + bandwidthRange: { min: 500e3, max: 20e6 }, + rateRange: { min: 100e3, max: 40e6 }, + streamTypes: { [DeviceStreamType.raw]: { dataTypes: [DataType.ci16] } }, + txRxDelay: 0, + warmupPackets: 0, +} + +export abstract class WebUsb extends EventTarget { + // static MAX_RECEIVE_DATA_REQUEST = 24; + static MAX_SEND_DATA_REQUEST = 128; + fd: number; + vid: number; + pid: number; + device: USBDevice | undefined; + module: ControlModule | undefined; + streamStatus: StreamStatus = 'STOPPED'; + protected _commands = new CircularBuffer(100); + protected _sendCommandInProcess: boolean = false; + // protected _sendDataRequest = new CircularBuffer(WebUsb.MAX_SEND_DATA_REQUEST); + protected _sendDataReqCnt = 0; + protected in = 0; + protected out = 0; + protected _start_ms = 0; + protected _end_ms = 0; + protected _onChangeSendDataReq?: () => void; + protected _onConnect: (this: USB, ev: USBConnectionEvent) => any; + protected _onDisconnect: (this: USB, ev: USBConnectionEvent) => any; + + abstract getConfiguration(): DeviceConfiguration; + abstract getRXSamplesCount(samples: number): number; + abstract getRXPacketSize(samples: number): number; + abstract getTXPacketSize(samples: number): number; + abstract decodeRxData(data: DataView, samples: number, opts?: RXDecoderOptions): Promise + abstract encodeTxData(data: TXBuffer, opts?: TXEncoderOptions): Promise + + static getDeviceName(device: USBDevice | undefined) { + let name = ''; + if (device) { + const vname = device.manufacturerName ? device.manufacturerName + ' ' : '0x' + device.vendorId.toString(16) + ':'; + const pname = device.productName ? device.productName : '0x' + device.productId.toString(16); + name = vname + pname; + } + return name; + } + + static getSampleByteLength(datatype: DataType): number { + return datatype === DataType.cf32 ? COMPLEX_FLOAT_SIZE : COMPLEX_INT16_SIZE; + } + + static getSamplesCnt(datatype: DataType, byteLength: number): number { + return Math.floor(byteLength / WebUsb.getSampleByteLength(datatype)); + } + + static fillData(outBufDataType: DataType, outBuf: ArrayBufferLike, outBufOffset: number, iqBufDataType: DataType, iqBuf: ArrayBufferLike, iqBufOffset?: number, iqBufLength?: number) { + // console.log('fillData: outBufDataType =', outBufDataType, ', outBuf =', outBuf, ', outBufOffset =', outBufOffset, ', iqBufDataType =', iqBufDataType, ', iqBuf =', iqBuf, ', iqBufOffset =', iqBufOffset, ', iqBufLength =', iqBufLength); + if (outBufDataType === DataType.cf32 && outBufDataType === iqBufDataType) { + const outBufView = new Float32Array(outBuf, outBufOffset, iqBufLength); + const iqBufView = new Float32Array(iqBuf, iqBufOffset, iqBufLength); + outBufView.set(iqBufView); + } else if (outBufDataType === DataType.ci16 && outBufDataType === iqBufDataType) { + const outBufView = new Int16Array(outBuf, outBufOffset, iqBufLength); + const iqBufView = new Int16Array(iqBuf, iqBufOffset, iqBufLength); + outBufView.set(iqBufView); + } else if (outBufDataType === DataType.ci16 && iqBufDataType === DataType.cf32) { + const outBufView = new Int16Array(outBuf, outBufOffset, iqBufLength); + const iqBufView = new Float32Array(iqBuf, iqBufOffset, iqBufLength); + bufferF32ToI16(iqBufView, outBufView); + } else if (outBufDataType === DataType.cf32 && iqBufDataType === DataType.ci16) { + const outBufView = new Float32Array(outBuf, outBufOffset, iqBufLength); + const iqBufView = new Int16Array(iqBuf, iqBufOffset, iqBufLength); + bufferI16ToF32(iqBufView, outBufView); + } + } + + constructor(parms: WebUsbParams) { + super() + this.fd = parms.fd; + this.vid = parms.vid; + this.pid = parms.pid; + this.module = parms.module; + this.device = undefined; + this._onConnect = this.onConnect.bind(this, navigator.usb); + this._onDisconnect = this.onDisconnect.bind(this, navigator.usb); + } + + protected set sendDataReqCnt(val: number) { + if (this._onChangeSendDataReq) this._onChangeSendDataReq(); + this._sendDataReqCnt = val; + } + + protected get sendDataReqCnt() { + return this._sendDataReqCnt; + } + + async waitForChangeSendDataReq(): Promise { + return new Promise(resolve => { + this._onChangeSendDataReq = () => { + resolve(); + this._onChangeSendDataReq = undefined; + }; + }); + } + + isOpened() { + return this.device && this.device.opened; + } + + getName() { + return WebUsb.getDeviceName(this.device); + } + + getSerialNumber() { + let sn = undefined; + if (this.device) { + sn = this.device.serialNumber && this.device.serialNumber !== '' ? this.device.serialNumber : '007'; + } + return sn; + } + + async open() { + if (debug_webusb) console.log('WebUsbBase.open()') + if (!this.module) { + console.error('Control module is not initialized'); + return; + } + this._sendCommandInProcess = false; + navigator.usb.removeEventListener("connect", this._onConnect); + navigator.usb.removeEventListener("disconnect", this._onDisconnect); + navigator.usb.addEventListener("connect", this._onConnect); + navigator.usb.addEventListener("disconnect", this._onDisconnect); + this.device = undefined; + const devices = await navigator.usb.getDevices(); + for (let device of devices) { + // console.log('DEVICE', device, this.vid, this.pid) + if (device.vendorId === this.vid && device.productId === this.pid) { + this.device = device; + // console.log('FOUND DEVICE', device) + break; + } + } + if (globalThis.debug_mode || debug_webusb) console.log('WebUsbBase.device', this.device) + } + + async close() { + if (debug_webusb) console.log('WebUsbBase.close()') + navigator.usb.removeEventListener("connect", this._onConnect); + navigator.usb.removeEventListener("disconnect", this._onDisconnect); + this._commands.clear(); + this.module?._close_device(this.fd); + await sleep(1); //waiting to finish all requests //!!! TODO: need to check finishing of the all requests + this.fd = -1; + await this.device?.close(); + this.device = undefined; + this._sendCommandInProcess = false; + } + + async write(ep: number, buf: BufferSource): Promise { + // if (ep === 3) { + // let view64 = new BigUint64Array(buf); + // console.log(`transferOut header: 0x${view64[0].toString(16)}, 0x${view64[1].toString(16)}`); + // } + return this.device?.transferOut(ep, buf); + } + + async read(ep: number, len: number): Promise { + return this.device?.transferIn(ep, len); + } + + async _sendCommand(req: Record): Promise> { + this._sendCommandInProcess = true; + let ret: Record = {}; + if (!this.module || !this.device) return { error: -1 }; + const in_cmd = this.module._malloc(512); + const out_res = this.module._malloc(512); + const req_str = JSON.stringify(req); + // console.log('REQ_STR', req_str) + this.module.stringToAscii(req_str, in_cmd); + // let res = await this.module._send_command(this.fd, in_cmd, req_str.length, out_res, 512); + if (debug_webusb) { + this._start_ms = Date.now(); + console.log('1. WebUsbBase._sendCommand: req = ', req) + } + const res = await this.module.ccall("send_command", "number", ["number", "number", "number", "number", "number"], + [this.fd, in_cmd, req_str.length, out_res, 512], { async: true } + ); + if (debug_webusb) { + this._end_ms = Date.now(); + console.log('2. WebUsbBase._sendCommand: res = ', res, 'duration', this._end_ms - this._start_ms) + } + + if (res != 0) { + ret = { error: res }; + } else { + const out_res_str = this.module.AsciiToString(out_res) + ret = await JSON.parse(out_res_str); + } + + this.module._free(in_cmd); + this.module._free(out_res); + + // console.log('sendCommand("', req, '") => ', res, ': ', ret); + this._sendCommandInProcess = false; + return ret; + } + + async runCommandPool() { + if (this._sendCommandInProcess) return; + this._sendCommandInProcess = true; + while (!this._commands.isEmpty()) { + const com_req = this._commands.front(); + if (com_req) { + try { + const rep = await this._sendCommand(com_req.req); + if (rep['error'] !== undefined) { + const err = rep['error'] + const errmsg = `WebUsb: Reply to command request '${JSON.stringify(com_req.req)}' contains error: ${err}`; + com_req.reject?.(errmsg); + } else { + com_req.resolve?.(rep); + } + } catch (err) { + const errmsg = `WebUsb: Reply to command request '${JSON.stringify(com_req.req)}' contains error: ${err}`; + com_req.reject?.(errmsg); + } + } + this._commands.pop_front(); + } + this._sendCommandInProcess = false; + } + + sendCommand(req: Record): Promise { + let com_req: CommandRequest = { req: req }; + const ret = new Promise((resolve, reject) => { + com_req.resolve = resolve; + com_req.reject = reject; + }); + this._commands.push_back(com_req); + this.runCommandPool(); + return ret; + } + + async sendDebugCommand(req: string): Promise { + if (!this.module || !this.device) return 'Error: module or device is undefined'; + const in_cmd = this.module._malloc(4096); + const out_res = this.module._malloc(4096); + // console.log('REQ_STR', req_str) + this.module.stringToAscii(req, in_cmd); + const res = await this.module.ccall("send_debug_command", "number", ["number", "number", "number", "number", "number"], + [this.fd, in_cmd, req.length, out_res, 4096], { async: true } + ); + + const ret = this.module.AsciiToString(out_res) + + this.module._free(in_cmd); + this.module._free(out_res); + + console.log('sendDebugCommand("', req, '") => ', res, ': ', ret); + return ret; + } + + async submitRxPacket(samples: number, opts?: RXDecoderOptions): Promise { + const bsz = this.getRXPacketSize(samples); + // console.log('submitRxPacket', samples, bsz); + return new Promise((extresolve, extreject) => { + this.read(WebUsbEndpoints.STREAM_EP, bsz) + .then((res: USBInTransferResult | undefined) => { + if (res && res.status == "ok" && res.data) { + // console.log('RECEIVED DATA AT', Date.now()) + return this.decodeRxData(res.data, samples, opts); + } else { + throw new Error(`WebUsb.submitRxPacket: Error: ${res?.status}`); + } + }) + .then((res: RXBuffer) => { + extresolve(res); + }) + .catch((err) => { + // normalize rejection to Error for callers + const e = err instanceof Error ? err : new Error(String(err)); + // attach optional id for debugging + if (opts?.id !== undefined) (e as any).id = opts.id; + extreject(e); + }) + }); + /*!!! this.read(WebUsb.STREAM_EP, bsz).then((res: USBInTransferResult | undefined) => { + if (res && res.status == "ok" && res.data) { + const data = res.data.buffer; + if (globalThis.debug_mode) + console.log(`WebUsb: got packet ${data.byteLength} bytes`); + this.dispatchEvent(new CustomEvent('rxdata', { detail: { res: res, data: data } })) + } else { + throw new Error(`WebUsb.submitRxPacket: Error: ${res?.status}`); + } + }).catch((err) => { + this.dispatchEvent(new CustomEvent('rxerror', { detail: { err: err } })) + });*/ + } + + protected async _sendTxPacket(pkt: ArrayBufferLike): Promise { + // console.log('_sendTxPacket', this._sendDataRequest.size()) + // if (this._sendDataReqCnt > 4) + // console.log('_sendTxPacket', this._sendDataReqCnt, 'AT', Date.now()) + // const view = new Uint32Array(pkt) + // console.log('_sendTxPacket', pkt.byteLength, view[0].toString(16), view[1].toString(16), view[2].toString(16), view[3].toString(16)) + // console.log(pkt); + return new Promise((extresolve, extreject) => { + this.write(WebUsbEndpoints.STREAM_EP, pkt as BufferSource) + .then((res: USBOutTransferResult | undefined) => { + if (res && res.status == "ok" && res.bytesWritten === pkt.byteLength) { + // console.log('_sendTxPacket reply', res, this._sendDataReqCnt, 'AT', Date.now()) + --this.sendDataReqCnt; + ++this.in; + extresolve({ usbOutTransferResult: res }); + } else { + throw new Error(`WebUsb.sendTxPacket: Error: ${res?.status}`); + } + }) + .catch((err) => { + // console.log('_sendTxPacket reply error', err, this._sendDataReqCnt, 'AT', Date.now()) + --this.sendDataReqCnt; + ++this.in; + extreject(err); + }) + }); + /*!!! this.write(WebUsb.STREAM_EP, pkt).then((res: USBOutTransferResult | undefined) => { + // this._sendDataRequest.pop_front(); + // console.log('SEND PKT res', this._sendDataRequest.size()) + // console.log('SEND PKT res', this._sendDataReqCnt) + if (res && res.status == "ok" && res.bytesWritten === pkt.byteLength) { + --this.sendDataReqCnt; + this.dispatchEvent(new CustomEvent('txdata', { detail: { res: res } })) + } else { + throw new Error(`WebUsb.sendTxPacket: Error: ${res?.status}`); + } + }).catch((err) => { + --this.sendDataReqCnt; + // this._sendDataRequest.pop_front(); + this.dispatchEvent(new CustomEvent('txerror', { detail: { err: err } })) + });*/ + } + + async sendTxRawPacket(pkt: ArrayBufferLike, allowDrop: boolean = false): Promise { + // let view32 = new Uint32Array(pkt); + // console.log(`send timestamp 1: ${view32[0]}, 0x${view32[1].toString(16)}, 0x${view32[2].toString(16)}, 0x${view32[3].toString(16)}`); + // view32 = new Uint32Array(pkt, 10016); + // console.log(`send timestamp 2: ${view32[0]}, 0x${view32[1].toString(16)}, 0x${view32[2].toString(16)}, 0x${view32[3].toString(16)}`); + // console.log('sendTxPacket', pkt) + if (this.sendDataReqCnt >= WebUsb.MAX_SEND_DATA_REQUEST) { + if (allowDrop) return Promise.reject(`packet dropped due queue is full (${this.sendDataReqCnt} >= ${WebUsb.MAX_SEND_DATA_REQUEST})`); + while (this.sendDataReqCnt >= WebUsb.MAX_SEND_DATA_REQUEST) await this.waitForChangeSendDataReq(); + } + ++this.sendDataReqCnt; + ++this.out; + // console.log('IN', this.in, 'OUT', this.out) + return this._sendTxPacket(pkt); + } + + async sendTxPacket(data: TXBuffer, opts?: TXEncoderOptions): Promise { + const buf = await this.encodeTxData(data, opts); + // console.log('BUF', buf); + return this.sendTxRawPacket(buf, opts !== undefined && opts.allowDrop !== undefined ? opts.allowDrop : false); + } + + async onConnect(usb: USB, event: USBConnectionEvent) { + if (globalThis.debug_mode) + console.log(`WebUsb: connection to device ${this.device?.vendorId}:${this.device?.productId} established`); + //!!! this.dispatchEvent(new Event('connect', event)); + return 0; + } + + async onDisconnect(usb: USB, event: USBConnectionEvent) { + if (globalThis.debug_mode) + console.log(`WebUsb: connection to device ${this.device?.vendorId}:${this.device?.productId} has been closed`); + //!!! this.dispatchEvent(new Event('disconnect', event)); + return 0; + } + + getStreamStatus(): StreamStatus { + return this.streamStatus; + } + + setStreamStatus(status: StreamStatus) { + this.streamStatus = status; + } +} + +export interface WebUsbParams { + max_send_data_requests?: number, + fd: number, + vid: number, + pid: number, + module: ControlModule, +} diff --git a/packages/frontend-core/src/webusb/webUsbControlApi.ts b/packages/frontend-core/src/webusb/webUsbControlApi.ts new file mode 100644 index 0000000..0448f34 --- /dev/null +++ b/packages/frontend-core/src/webusb/webUsbControlApi.ts @@ -0,0 +1,97 @@ +import { WebUsbEndpoints } from "./webUsbBase"; +// import { WASMErrno } from "@/common/wasmErrno"; + +const debug_ep_log = false; +const debug_write_log = false; + +export async function write_ep1(fd: number, data: number, len: number): Promise { + // const start = Date.now(); + if (globalThis.debug_mode || debug_ep_log) + console.log(`write_ep1(${fd}, ${data}, ${len}): `/* start = ${start}` */) + const dev = globalThis.webUsbDeviceManager?.getDevice(fd); + if (!dev || !dev.module || !dev.write) { + console.error('write_ep1 error: dev = ', dev, 'webUsbWorkerManager', globalThis.webUsbDeviceManager, 'fd', fd) + return 0; + } + const buf = new Uint8Array(dev.module.HEAPU8.buffer, ((data) >> 0), len); + if (globalThis.debug_mode) console.log(buf.subarray(0, 10)) + dev.write(WebUsbEndpoints.CONTROL_EP, buf as BufferSource); + // const end = Date.now(); + // console.log(`write_ep1(${fd}, ${data}, ${len}): duration = ${end - start}`) + return len; +} + +export async function write_ep2(fd: number, data: number, len: number): Promise { + // const start = Date.now(); + if (globalThis.debug_mode || debug_ep_log) + console.log(`write_ep2(${fd}, ${data}, ${len}): `/* start = ${start}` */) + const dev = globalThis.webUsbDeviceManager?.getDevice(fd); + if (!dev || !dev.module || !dev.write) { + console.error('write_ep2 error: dev = ', dev, 'webUsbWorkerManager', globalThis.webUsbDeviceManager, 'fd', fd) + return 0; + } + const buf = new Uint8Array(dev.module.HEAPU8.buffer, ((data) >> 0), len); + if (globalThis.debug_mode) console.log(buf.subarray(0, 10)) + dev.write(WebUsbEndpoints.NOTIFY_EP, buf as BufferSource); + // const end = Date.now(); + // console.log(`write_ep2(${fd}, ${data}, ${len}): duration = ${end - start}`) + return len; +} + +export async function read_ep1(fd: number, data: number, len: number): Promise { + // const start = Date.now(); + if (globalThis.debug_mode || debug_ep_log) + console.log(`read_ep1(${fd}, ${data}, ${len}): `/* start = ${start}` */) + const dev = globalThis.webUsbDeviceManager?.getDevice(fd); + if (!dev || !dev.module || !dev.read) { + console.error('read_ep1 error: dev = ', dev, 'webUsbWorkerManager', globalThis.webUsbDeviceManager, 'fd', fd) + return 0; + } + const result = await dev.read(WebUsbEndpoints.CONTROL_EP, 512); + // const end = Date.now(); + // console.log(`read_ep1(${fd}, ${data}, ${len}): duration = ${end - start}`) + + if (result && result.data && result.status == "ok") { + const buf = new Uint8Array(dev.module.HEAPU8.buffer, ((data) >> 0), len); + const readbackvalue = new Uint8Array(result.data.buffer); + buf.set(readbackvalue); + if (globalThis.debug_mode) + console.log(` => rb ${readbackvalue}`); + return readbackvalue.length; + } + return -22; +} + +export async function read_ep2(fd: number, data: number, len: number): Promise { + // const start = Date.now(); + if (globalThis.debug_mode || debug_ep_log) + console.log(`read_ep2(${fd}, ${data}, ${len}): `/* start = ${start}` */) + const dev = globalThis.webUsbDeviceManager?.getDevice(fd); + if (!dev || !dev.module || !dev.read) { + console.error('read_ep2 error: dev = ', dev, 'webUsbWorkerManager', globalThis.webUsbDeviceManager, 'fd', fd) + return 0; + } + const result = await dev.read(WebUsbEndpoints.NOTIFY_EP, 64); + // const end = Date.now(); + // console.log(`read_ep2(${fd}, ${data}, ${len}): duration = ${end - start}`) + + if (result && result.data && result.status == "ok") { + const buf = new Uint8Array(dev.module.HEAPU8.buffer, ((data) >> 0), len); + const readbackvalue = new Uint8Array(result.data.buffer); + buf.set(readbackvalue); + if (globalThis.debug_mode) + console.log(` => ntfy ${readbackvalue}`); + return readbackvalue.length; + } + return -22; +} + +export async function write_log_js(fd: number, severity: number, str: number): Promise { + if (globalThis.debug_mode || debug_write_log) { + const dev = globalThis.webUsbDeviceManager?.getDevice(fd); + if (!dev || !dev.module) return 0; + var s = dev.module.AsciiToString(str); + console.log(`write_log_js(${fd}, ${severity}, ${s})`) + } + return 0; +} diff --git a/packages/frontend-core/src/webusb/webUsbDeviceManager.ts b/packages/frontend-core/src/webusb/webUsbDeviceManager.ts new file mode 100644 index 0000000..d11099a --- /dev/null +++ b/packages/frontend-core/src/webusb/webUsbDeviceManager.ts @@ -0,0 +1,97 @@ +import initControlModule from '@/control/control' +import type { ControlModule } from '@/control/control' +import type { WebUsb } from './webUsbBase'; +import { getDeviceHash, getWebUsbInstance } from './webUsb'; + +declare global { + var webUsbDeviceManager: WebUsbDeviceManager | undefined; +} + +export async function initControl(overrides?: Partial) { + if (globalThis.controlModule === undefined) + globalThis.controlModule = await initControlModule(overrides); + if (globalThis.webUsbDeviceManager === undefined && globalThis.controlModule !== undefined) { + globalThis.webUsbDeviceManager = new WebUsbDeviceManager({ module: globalThis.controlModule }); + } + // console.log('MODULE', controlModule, 'WebUsbManager', globalThis.webUsbDeviceManager) +} + +export function isControlInitialized(): boolean { + return globalThis.controlModule !== undefined && globalThis.webUsbDeviceManager !== undefined; +} + +export class WebUsbDeviceManager { + public module: ControlModule | undefined; + protected webUsbDevices: Array; + + constructor(params: WebUsbManagerParams) { + this.webUsbDevices = []; + this.module = params.module; + } + + async open(vid: number, pid: number): Promise { + if (!this.module) { + console.error('Error: control module is not defined') + return undefined; + } + + for (let i = 0; i < this.webUsbDevices.length; ++i) { + const dev = this.webUsbDevices[i]; + if (dev !== undefined && dev.vid === vid && dev.pid === pid) { + //TODO: Replace with checking serial number when it will be actual + // console.log('FOUND OPENED DEVICE', this.webUsbDevices[i]) + return this.webUsbDevices[i]; + } + } + + let fd = -1 + for (let i = 0; i < this.webUsbDevices.length; ++i) { + if (this.webUsbDevices[i] == undefined) { + fd = i; + break; + } + } + // Next line means that we didn't find any free slot, so we replace the first one. + // We can't add new one because it should consistently work with C side array. + if (fd < 0) fd = 0; + this.webUsbDevices[fd] = getWebUsbInstance(getDeviceHash({ vendorId: vid, productId: pid }), { fd: fd, module: this.module, vid: vid, pid: pid }); + if (this.webUsbDevices[fd] === undefined) { + console.error(`Error: unsupported WebUSB device vid=0x${vid.toString(16)}, pid=0x${pid.toString(16)}`); + return undefined; + } + await this.webUsbDevices[fd]?.open(); + return this.webUsbDevices[fd]; + } + + async close(fd: number): Promise { + if (fd < 0 || fd >= this.webUsbDevices.length || this.webUsbDevices[fd] == undefined) return false; + await this.webUsbDevices[fd]?.close(); + this.webUsbDevices[fd] = undefined; + return true; + } + + async closeAll() { + for (let fd = 0; fd < this.webUsbDevices.length; ++fd) { + await this.close(fd); + } + } + + getDevice(fd: number): WebUsb | undefined { + if (fd < 0 || fd >= this.webUsbDevices.length || this.webUsbDevices[fd] == undefined) return undefined; + return this.webUsbDevices[fd]; + } + + getDevices(): Array { + const ret = new Array(); + for (let i = 0; i < this.webUsbDevices.length; ++i) { + const dev = this.webUsbDevices[i]; + if (dev !== undefined) ret.push(dev.fd); + } + + return ret; + } +} + +export interface WebUsbManagerParams { + module: ControlModule, +} diff --git a/packages/frontend-core/src/webusb/webUsbLimeSdr.ts b/packages/frontend-core/src/webusb/webUsbLimeSdr.ts new file mode 100644 index 0000000..e69fc44 --- /dev/null +++ b/packages/frontend-core/src/webusb/webUsbLimeSdr.ts @@ -0,0 +1,204 @@ +import { WebUsb, DeviceStreamType } from './webUsbBase'; +import type { + WebUsbParams, RXDecoderOptions, RXBuffer, DeviceConfiguration, TXBuffer, TXEncoderOptions +} from './webUsbBase'; +import { + DataType, COMPLEX_FLOAT_SIZE, COMPLEX_INT16_SIZE +} from '@websdr/core/common'; + + +const debug_usb_log = false; + +export class WebUsbLimeSdr extends WebUsb { + static PACKET_SIZE = 4096; + static HEADER_SIZE = 16; + static SAMPLES_PER_PACKET = ((WebUsbLimeSdr.PACKET_SIZE - WebUsbLimeSdr.HEADER_SIZE) / COMPLEX_INT16_SIZE); + static ELEMENTS_PER_PACKET = WebUsbLimeSdr.SAMPLES_PER_PACKET << 1; + static SAMPLES_MULTIPLY = 4; + + protected _lastRecvTimestamp = 0n; + + constructor(parms: WebUsbParams) { + super(parms) + if (globalThis.debug_mode || debug_usb_log) console.log('Created WebUsbLimeSdr') + } + + getConfiguration(): DeviceConfiguration { + return { + defaultSamplesCount: WebUsbLimeSdr.SAMPLES_PER_PACKET * 8, + rxFrequencyRange: { min: 30e6, max: 3800e6 }, + txFrequencyRange: { min: 30e6, max: 3800e6 }, + bandwidthRange: { min: 500e3, max: 40e6 }, + rateRange: { min: 192e3, max: 40e6 }, + streamTypes: { [DeviceStreamType.raw]: { dataTypes: [DataType.ci16] } }, + txRxDelay: 94, + warmupPackets: 500, + }; + } + + getRXSamplesCount(samples: number): number { + // Each packet contains 16 bytes header and 1020 iq samples + return Math.floor(samples / WebUsbLimeSdr.SAMPLES_PER_PACKET) * WebUsbLimeSdr.SAMPLES_PER_PACKET; + } + + getRXPacketSize(samples: number): number { + // Each packet contains 16 bytes header and 1020 iq samples + return Math.ceil(samples / WebUsbLimeSdr.SAMPLES_PER_PACKET) * WebUsbLimeSdr.PACKET_SIZE; + } + + getTXPacketSize(samples: number): number { + // Each packet contains 16 bytes header and 1020 iq samples + const lastPktFill = samples % WebUsbLimeSdr.SAMPLES_PER_PACKET; + return Math.floor(samples / WebUsbLimeSdr.SAMPLES_PER_PACKET) * WebUsbLimeSdr.PACKET_SIZE + + (lastPktFill > 0 ? WebUsbLimeSdr.HEADER_SIZE + lastPktFill * COMPLEX_INT16_SIZE : 0); + } + + protected _fillHeader(buf: ArrayBufferLike, offset: number, discard_timestamp: boolean, timestamp: bigint, samples: number) { + const discard_timestamp_flag = discard_timestamp ? 1 : 0; + /* + 16 bytes packet header + + DB0: [4] discard_timestamp_flag + DB1: [7:0] samples_size_bytes[7:0] + DB2: [15:8] samples_size_bytes[15:8] + DB3: 00 + DB1: 00000000 + DW2: [31:0] timestamp[31:0] + DW3: [31:0] timestamp[64:32] + + DW4-N IQ 16 bit + */ + const bufLen = (buf as ArrayBufferLike).byteLength || 0; + if (bufLen - offset < WebUsbLimeSdr.HEADER_SIZE) + throw new Error(`WebUsbLimeSdr.fillHeader: Error: buffer slice must be at least ${WebUsbLimeSdr.HEADER_SIZE} bytes (offset ${offset})`); + + if (samples % 4 !== 0) + throw new Error(`WebUsbLimeSdr.fillHeader: Error: count of samples must be a multiple of 4`) + // if (samples < WebUsbLimeSdr.MIN_SAMPLES_IN_PKT || samples > WebUsbLimeSdr.MAX_SAMPLES_IN_PKT) + // throw new Error(`WebUsbLimeSdr.fillHeader: Error: count of samples in the packet must be in range [${WebUsbLimeSdr.MIN_SAMPLES_IN_PKT}, ${WebUsbLimeSdr.MAX_SAMPLES_IN_PKT}]`) + + const dataView = new DataView(buf, offset, WebUsbLimeSdr.HEADER_SIZE); + dataView.setUint8(0, discard_timestamp_flag << 4); + dataView.setUint16(1, samples * COMPLEX_INT16_SIZE, true); + dataView.setUint8(3, 0); + dataView.setUint32(4, 0); + dataView.setBigUint64(8, timestamp, true); + // console.log('TX HEADER', dataView.getUint8(0), dataView.getUint16(1, true), dataView.getBigUint64(8, true)); + } + + async decodeRxData(data: DataView, samples: number, opts?: RXDecoderOptions): Promise { + if (globalThis.debug_mode || debug_usb_log) console.log('RECEIVED DATA', data) + const id = opts !== undefined && opts.id !== undefined ? opts.id : -1; + const packetCnt = (data.byteLength / WebUsbLimeSdr.PACKET_SIZE) >> 0; + const dataSize = data.byteLength - WebUsbLimeSdr.HEADER_SIZE * packetCnt; + const samplesRecv = (dataSize / COMPLEX_INT16_SIZE) >> 0; + // console.log('samplesRecv =', samplesRecv, ', samples =', samples) + if (samplesRecv < samples) { + throw new Error(`WebUsbLimeSdr.parseRxData: Unexpected usb packet size (${samplesRecv} received < ${samples} expected)`); + } + const timestamp = data.getBigUint64(8, true); + if (timestamp - this._lastRecvTimestamp > 100000) { + console.error('VERY BIG DIFFERENCE', data, timestamp, this._lastRecvTimestamp, timestamp - this._lastRecvTimestamp); + } + let prevPacketTS = timestamp; + let overrun = 0; + + const datatype = opts?.datatype || DataType.ci16; + const elementSize = (datatype === DataType.cf32 || datatype === DataType.f32) ? COMPLEX_FLOAT_SIZE : COMPLEX_INT16_SIZE; + const output = opts?.data !== undefined ? opts.data : new SharedArrayBuffer(samplesRecv * elementSize); + + let freeSamples = samples; + for (let i = 0; i < packetCnt; ++i) { + const outBufOffset = i * WebUsbLimeSdr.SAMPLES_PER_PACKET * elementSize; + const packetOffset = i * WebUsbLimeSdr.PACKET_SIZE; + const packetTS = data.getBigUint64(packetOffset + 8, true); + const samplesDelta = Number(packetTS - prevPacketTS) - WebUsbLimeSdr.SAMPLES_PER_PACKET; + if (samplesDelta > 0) { + overrun += (samplesDelta / WebUsbLimeSdr.SAMPLES_PER_PACKET) >> 0; + } + prevPacketTS = packetTS; + const samplesCur = Math.min(WebUsbLimeSdr.SAMPLES_PER_PACKET, freeSamples); + if (samplesCur > 0) { + WebUsb.fillData(datatype, output, outBufOffset, DataType.ci16, data.buffer, packetOffset + WebUsbLimeSdr.HEADER_SIZE, samplesCur << 1); + freeSamples -= samplesCur; + } + } + + if (globalThis.debug_mode || debug_usb_log) console.log('RECEIVE timestamp', timestamp - this._lastRecvTimestamp, timestamp, this._lastRecvTimestamp, 'output', output.byteLength); + const ret: RXBuffer = { + data: output, + datatype: datatype, + id: id, + samples: samplesRecv, + timestamp: timestamp, + overrun: overrun, + realigned: 0, + dropped: 0, + recvsize: data.byteLength, + } + + this._lastRecvTimestamp = timestamp; + + return ret; + } + + async encodeTxData(data: TXBuffer, opts?: TXEncoderOptions): Promise { + if (globalThis.debug_mode || debug_usb_log) console.log('DATA TO SEND', data, data.data.slice(0, 10)); + // console.log('SEND TIMESTAMP', data.timestamp, data.byteLength /* , data.data.slice(0, 10), data.data.slice(data.size - 10, data.size) */); + // const viewin = new Int16Array(data.data, data.byteOffset, data.byteLength); + // console.log('VIEWIN', viewin.length, viewin); + let output = undefined; + const samplesCnt = WebUsb.getSamplesCnt(data.datatype, data.byteLength || data.data.byteLength); + const bufSize = this.getTXPacketSize(samplesCnt); + output = opts?.data !== undefined ? opts.data : new ArrayBuffer(bufSize); + if (output.byteLength < bufSize) + throw new Error(`WebUsbLimeSdr.encodeTxData: Error: buffer length must be more than ${bufSize} bytes`); + + const pktCnt = Math.ceil(samplesCnt / WebUsbLimeSdr.SAMPLES_PER_PACKET); + let timestamp = data.timestamp; + if (!data.discard_timestamp && timestamp < this._lastRecvTimestamp) { + console.warn('Send timestamp', timestamp, ' less than last received timestamp', this._lastRecvTimestamp); + } + const timestampInc = BigInt(WebUsbLimeSdr.SAMPLES_PER_PACKET); + const dataOffsetMain = data.byteOffset || 0; + const dataOffsetMult = WebUsbLimeSdr.SAMPLES_PER_PACKET * WebUsb.getSampleByteLength(data.datatype); + // console.log(`samplesCnt=${samplesCnt}, bufSize=${bufSize}, pktCnt=${pktCnt}`) + for (let i = 0; i < pktCnt; ++i) { + const pktOffset = i * WebUsbLimeSdr.PACKET_SIZE; + const dataOffset = dataOffsetMain + i * dataOffsetMult; + const samples = i < pktCnt - 1 ? WebUsbLimeSdr.SAMPLES_PER_PACKET : samplesCnt - i * WebUsbLimeSdr.SAMPLES_PER_PACKET; + this._fillHeader(output, pktOffset, data.discard_timestamp, timestamp, samples); + WebUsb.fillData(DataType.ci16, output, pktOffset + WebUsbLimeSdr.HEADER_SIZE, data.datatype, data.data, dataOffset, samples << 1); + timestamp += timestampInc; + } + // const view = new Int16Array(output/* , WebUsbLimeSdr.HEADER_SIZE */); + // console.log('TIMESTAMP', data.timestamp, view.length, view /*, view.subarray(0, 10), view.subarray(view.length - 10, view.length) */); + return output; + } + + async open() { + await super.open(); + + if (this.module && this.device) { + try { + // if (!this.device.opened) await this.device.close(); + await this.device.open(); + try { + await this.device.reset(); //!!! this causes an error in windows + } catch (err) { + console.warn('Seems you use OS Windows and reset of usb device causes an error: ', err); + } + if (this.device.configuration === null) await this.device.selectConfiguration(1); + await this.device.claimInterface(0); + await this.device.claimInterface(1); + const res = await this.module.ccall("init_lib", "number", ["number", "number", "number"], + [this.fd, this.device.vendorId, this.device.productId], { async: true } + ); + if (res < 0) this.device.close(); + } catch (err) { + console.error(err) + this.fd = -1; + } + } + } +} diff --git a/packages/frontend-core/src/webusb/webUsbManager.ts b/packages/frontend-core/src/webusb/webUsbManager.ts new file mode 100644 index 0000000..e951ca3 --- /dev/null +++ b/packages/frontend-core/src/webusb/webUsbManager.ts @@ -0,0 +1,454 @@ +import { WebUsb, DefaultDeviceConfiguration } from './webUsbBase'; +import type { + StreamStatus, RXBuffer, RXDecoderOptions, DeviceConfiguration, + TXBuffer, TXResult, TXEncoderOptions +} from './webUsbBase'; +import { SDRDevicesIds } from './webUsb'; +import { PromiseHelper } from '@websdr/core/utils'; +import type { WebUsbWorkerResponse } from './webUsb.worker.types'; + + +let webUsbManager: Array = [undefined, undefined, undefined, undefined]; + +export enum WebUsbManagerMode { + UNKNOWN = 0, + SINGLE = 1, + WORKER = 2, +}; + +export function getWebUsbManagerInstance(mode: WebUsbManagerMode): WebUsbManager { + if (webUsbManager[mode] === undefined) { + switch (mode) { + case WebUsbManagerMode.SINGLE: + webUsbManager[mode] = new WebUsbSingleManager(); + break; + case WebUsbManagerMode.WORKER: + webUsbManager[mode] = new WebUsbWorkerManager(); + break; + } + } + if (webUsbManager[mode] === undefined) throw new Error("webUsbManager[mode] can't be undefined") + return webUsbManager[mode]!; +} + +export interface RequestDeviceInfo { + devName: string, + vendorId: number, + productId: number, +} + +export abstract class WebUsbManager { + protected _filters: Array>; + + constructor() { + this._filters = SDRDevicesIds; + } + + async requestDevice(): Promise { + try { + const device = await navigator.usb.requestDevice({ + filters: this._filters, + }); + if (device) { + // await device.open(); + const devName = WebUsb.getDeviceName(device); + const vendorId = device.vendorId; + const productId = device.productId; + + return { devName, vendorId, productId } + } + } catch (err) { + console.error('requestDevice: error:', err); + } + return undefined; + } + + abstract open(vendorId?: number, productId?: number): Promise; + abstract close(fd: number): Promise; + abstract closeAll(): Promise; + abstract getName(fd: number): Promise; + abstract getSerialNumber(fd: number): Promise; + abstract getRXSamplesCount(fd: number, samples: number): Promise; + abstract sendCommand(fd: number, req: Record): Promise>; + abstract sendDebugCommand(fd: number, req: string): Promise; + abstract submitRxPacket(fd: number, samples: number, opts?: RXDecoderOptions): Promise; + abstract sendTxPacket(fd: number, data: TXBuffer, opts?: TXEncoderOptions): Promise; + abstract getStreamStatus(fd: number): Promise; + abstract setStreamStatus(fd: number, status: StreamStatus): Promise; + abstract getConfiguration(fd: number): Promise; + abstract getOpenedDeviceList(): Promise | undefined>; +} + +class WebUsbSingleManager extends WebUsbManager { + constructor() { + super() + } + + async open(vendorId?: number, productId?: number): Promise { + // console.warn('WebUsbSingleManager.open', vendorId, productId); + if (vendorId === undefined || productId === undefined) return -1; + const dev = await globalThis.webUsbDeviceManager?.open(vendorId, productId); + if (!dev) return -1; + return dev.fd; + } + + async close(fd: number) { + // console.warn('WebUsbSingleManager.close', fd); + if (fd < 0) return; + await globalThis.webUsbDeviceManager?.close(fd); + } + + async closeAll() { + // console.warn('WebUsbSingleManager.closeAll'); + await globalThis.webUsbDeviceManager?.closeAll(); + } + + async getName(fd: number): Promise { + const dev = globalThis.webUsbDeviceManager?.getDevice(fd); + if (!dev) return ''; + return dev.getName(); + } + + async getSerialNumber(fd: number): Promise { + const dev = globalThis.webUsbDeviceManager?.getDevice(fd); + if (!dev) return ''; + return dev.getSerialNumber() ?? ''; + } + + async getRXSamplesCount(fd: number, samples: number): Promise { + return globalThis.webUsbDeviceManager?.getDevice(fd)?.getRXSamplesCount(samples) || 0; + } + + async sendCommand(fd: number, req: Record): Promise> { + const dev = globalThis.webUsbDeviceManager?.getDevice(fd); + return dev ? dev.sendCommand(req) : {}; + } + + async sendDebugCommand(fd: number, req: string): Promise { + const dev = globalThis.webUsbDeviceManager?.getDevice(fd); + return dev ? dev.sendDebugCommand(req) : ''; + } + + async submitRxPacket(fd: number, samples: number, opts?: RXDecoderOptions): Promise { + return new Promise((resolve, reject) => { + const dev = globalThis.webUsbDeviceManager?.getDevice(fd); + // console.log('SUBMIT RX', dev, fd, samples, opts) + if (!dev) reject({ err: 'Device is not defined', id: opts?.id }) + dev?.submitRxPacket(samples, opts) + .then((data: RXBuffer) => { + resolve(data); + }) + .catch(err => reject(err)); + }) + } + + async sendTxPacket(fd: number, data: TXBuffer, opts?: TXEncoderOptions): Promise { + const dev = globalThis.webUsbDeviceManager?.getDevice(fd); + return dev ? dev.sendTxPacket(data, opts) : {}; + } + + async getStreamStatus(fd: number): Promise { + const dev = globalThis.webUsbDeviceManager?.getDevice(fd); + return dev ? dev.getStreamStatus() : 'INVALID'; + } + + async setStreamStatus(fd: number, status: StreamStatus): Promise { + const dev = globalThis.webUsbDeviceManager?.getDevice(fd); + dev?.setStreamStatus(status); + } + + async getConfiguration(fd: number): Promise { + const dev = globalThis.webUsbDeviceManager?.getDevice(fd); + return dev?.getConfiguration() || DefaultDeviceConfiguration; + } + + async getOpenedDeviceList(): Promise | undefined> { + if (!globalThis.webUsbDeviceManager) return undefined; + const ret = new Array(); + const dev_fds = globalThis.webUsbDeviceManager.getDevices(); + if (dev_fds !== undefined) { + dev_fds.forEach(fd => { + const dev = globalThis.webUsbDeviceManager!.getDevice(fd); + if (dev) ret.push({ devName: WebUsb.getDeviceName(dev.device), vendorId: dev.vid, productId: dev.pid }) + }) + } + return ret; + } +} + +class WebUsbWorkerManager extends WebUsbManager { + protected worker: Worker | undefined = undefined; + protected _promiseHelper: PromiseHelper = new PromiseHelper(); + protected _onWorkerMessage: EventListenerOrEventListenerObject; + protected _onWorkerError: EventListenerOrEventListenerObject; + + constructor() { + super() + this._onWorkerMessage = this.onWorkerMessage.bind(this) as EventListenerOrEventListenerObject; + this._onWorkerError = this.onWorkerError.bind(this) as EventListenerOrEventListenerObject; + this.startWorker(); + } + + async open(vendorId?: number, productId?: number): Promise { + // console.warn('WebUsbWorkerManager.open', vendorId, productId); + if (vendorId === undefined || productId === undefined) return -1; + if (!this.worker) { + await this.startWorker(); + if (!this.worker) throw new Error('WebUsbManager: error creating worker'); + } + const [id, ret] = this._promiseHelper.createPromise(); + this.worker.postMessage({ type: 'OPEN', id: id, vendorId: vendorId, productId: productId }); + return ret; + } + + async close(fd: number): Promise { + // console.warn('WebUsbWorkerManager.close', fd); + if (!this.worker) throw new Error('WebUsbManager: worker is not running'); + if (fd < 0) return; + const [id, ret] = this._promiseHelper.createPromise(); + this.worker.postMessage({ type: 'CLOSE', id: id, fd: fd }); + return ret; + } + + async closeAll(): Promise { + // console.warn('WebUsbWorkerManager.closeAll'); + if (!this.worker) throw new Error('WebUsbManager: worker is not running'); + const [id, ret] = this._promiseHelper.createPromise(); + this.worker.postMessage({ type: 'CLOSE_ALL', id: id }); + return ret; + } + + async getName(fd: number): Promise { + if (!this.worker) throw new Error('WebUsbManager: worker is not running'); + const [id, ret] = this._promiseHelper.createPromise() + this.worker.postMessage({ type: 'GET_DEV_NAME', id: id, fd: fd }); + return ret; + } + + async getSerialNumber(fd: number): Promise { + if (!this.worker) throw new Error('WebUsbManager: worker is not running'); + const [id, ret] = this._promiseHelper.createPromise() + this.worker.postMessage({ type: 'GET_SERIAL_NUMBER', id: id, fd: fd }); + return ret; + } + + async getRXSamplesCount(fd: number, samples: number): Promise { + if (!this.worker) throw new Error('WebUsbManager: worker is not running'); + const [id, ret] = this._promiseHelper.createPromise() + this.worker.postMessage({ type: 'GET_RX_SAMPLES_COUNT', id: id, fd: fd, samples: samples }); + return ret; + } + + async sendCommand(fd: number, req: Record): Promise> { + if (!this.worker) throw new Error('WebUsbManager: worker is not running'); + const [id, ret] = this._promiseHelper.createPromise>() + this.worker.postMessage({ type: 'SEND_COMMAND', id: id, fd: fd, req: req }); + return ret; + } + + async sendDebugCommand(fd: number, req: string): Promise { + if (!this.worker) throw new Error('WebUsbManager: worker is not running'); + const [id, ret] = this._promiseHelper.createPromise() + this.worker.postMessage({ type: 'SEND_DEBUG_COMMAND', id: id, fd: fd, req: req }); + return ret; + } + + async submitRxPacket(fd: number, samples: number, opts?: RXDecoderOptions): Promise { + if (!this.worker) throw new Error('WebUsbManager: worker is not running'); + const [id, ret] = this._promiseHelper.createPromise() + // const data = new ArrayBuffer(samples * COMPLEX_INT16_SIZE + ControlWebUsb.TRAILER_SIZE) + if (opts && opts.data && opts.data instanceof ArrayBuffer) + this.worker.postMessage({ type: 'SUBMIT_RX_PACKET', id: id, fd: fd, samples: samples, opts: opts }, [opts.data]); + else + this.worker.postMessage({ type: 'SUBMIT_RX_PACKET', id: id, fd: fd, samples: samples, opts: opts }); + return ret; + } + + async sendTxPacket(fd: number, data: TXBuffer, opts?: TXEncoderOptions): Promise { + if (!this.worker) throw new Error('WebUsbManager: worker is not running'); + const [id, ret] = this._promiseHelper.createPromise() + if (data && data.data && data.data instanceof ArrayBuffer) + this.worker.postMessage({ type: 'SEND_TX_PACKET', id: id, fd: fd, data: data, opts: opts }, [data.data]); + else + this.worker.postMessage({ type: 'SEND_TX_PACKET', id: id, fd: fd, data: data, opts: opts }); + return ret; + } + + async getStreamStatus(fd: number): Promise { + if (!this.worker) throw new Error('WebUsbManager: worker is not running'); + const [id, ret] = this._promiseHelper.createPromise() + this.worker.postMessage({ type: 'GET_STREAM_STATUS', id: id, fd: fd }); + return ret; + } + + async setStreamStatus(fd: number, status: StreamStatus): Promise { + if (!this.worker) throw new Error('WebUsbManager: worker is not running'); + const [id, ret] = this._promiseHelper.createPromise() + this.worker.postMessage({ type: 'SET_STREAM_STATUS', id: id, fd: fd, status: status }); + return ret; + } + + async getConfiguration(fd: number): Promise { + if (!this.worker) throw new Error('WebUsbManager: worker is not running'); + const [id, ret] = this._promiseHelper.createPromise() + this.worker.postMessage({ type: 'GET_CONFIGURATION', id: id, fd: fd }); + return ret; + } + + + async getOpenedDeviceList(): Promise | undefined> { + if (!this.worker) throw new Error('WebUsbManager: worker is not running'); + const [id, ret] = this._promiseHelper.createPromise | undefined>() + this.worker.postMessage({ type: 'GET_OPENED_DEVICE_LIST', id: id }); + return ret; + } + + protected async startWorker(): Promise { + // console.log('Start WebUsbWorker') + if (this.worker) this.stopWorker(); + this.worker = new Worker(new URL('./webUsb.worker.js', import.meta.url), { type: 'module' }); + this.worker.addEventListener('message', this._onWorkerMessage); + this.worker.addEventListener('error', this._onWorkerError); + const [id, ret] = this._promiseHelper.createPromise(); + this.worker.postMessage({ type: 'START', id: id }); + return ret; + } + + protected async stopWorker(): Promise { + // console.log('Stop WebUsbWorker') + if (this.worker !== undefined) { + const [id, ret] = this._promiseHelper.createPromise(); + this.worker.postMessage({ type: 'STOP', id: id }); + return ret; + } + return; + } + + protected onStopWorker() { + if (this.worker !== undefined) { + this.worker.removeEventListener('message', this._onWorkerMessage); + this.worker.removeEventListener('error', this._onWorkerError); + this.worker.terminate(); + this.worker = undefined; + } + } + + protected onWorkerMessage(event: MessageEvent) { + const msg = event.data as WebUsbWorkerResponse; + if (globalThis.debug_mode) console.log('Message from WebUsbWorker', msg) + + let promise = undefined; + if (typeof msg.id === 'number') promise = this._promiseHelper.getPromise(msg.id); + + switch (msg.type) { + case 'STOP': + this.onStopWorker(); + // intentional fall-through: also resolve any pending promise for STOP using the common branch below + // eslint-disable-next-line no-fallthrough + case 'START': + case 'OPEN': + case 'CLOSE': + case 'CLOSE_ALL': + case 'GET_DEV_NAME': + case 'GET_SERIAL_NUMBER': + case 'GET_RX_SAMPLES_COUNT': + case 'SEND_COMMAND': + case 'SEND_DEBUG_COMMAND': + case 'SUBMIT_RX_PACKET': + case 'SEND_TX_PACKET': + case 'GET_STREAM_STATUS': + case 'SET_STREAM_STATUS': + case 'GET_CONFIGURATION': + if (promise) { + if (msg.res === 'ok') { + this._promiseHelper.promiseResolve(promise, msg.ret); + } else { + this._promiseHelper.promiseReject(promise, msg.err); + } + } + break; + case 'GET_OPENED_DEVICE_LIST': + if (promise) { + if (msg.res === 'ok') { + this._promiseHelper.promiseResolve(promise, msg.ret); + } else { + this._promiseHelper.promiseReject(promise, msg.err); + } + } + break; + default: + console.error('WebUsbWorkerManager: Unknown message', msg, 'from WebUsbWorker was received') + } + if (typeof msg.id === 'number' && promise !== undefined) this._promiseHelper.deletePromise(msg.id); + } + + protected onWorkerError(evt: Event) { + const event: ErrorEvent = evt as ErrorEvent + console.error('WebUsbWorkerManager: Worker error: ', event.message) + } +} + +export class WebUsbDummyManager extends WebUsbManager { + protected _stream_status: StreamStatus = 'INVALID'; + + constructor() { + super() + } + + async open(vendorId?: number, productId?: number): Promise { + this._stream_status = 'PREPARED'; + return 0; + } + + async close(fd: number) { + this._stream_status = 'INVALID'; + } + + async closeAll() { + this._stream_status = 'INVALID'; + } + + async getName(fd: number): Promise { + return ''; + } + + async getSerialNumber(fd: number): Promise { + return ''; + } + + async getRXSamplesCount(fd: number, samples: number): Promise { + return samples || 0; + } + + async sendCommand(fd: number, req: Record): Promise> { + return {}; + } + + async sendDebugCommand(fd: number, req: string): Promise { + return ''; + } + + async submitRxPacket(fd: number, samples: number, opts?: RXDecoderOptions): Promise { + return Promise.reject({ err: 'This is dummy device', id: opts?.id }) + } + + async sendTxPacket(fd: number, data: TXBuffer, opts?: TXEncoderOptions): Promise { + return {}; + } + + async getStreamStatus(fd: number): Promise { + return this._stream_status; + } + + async setStreamStatus(fd: number, status: StreamStatus): Promise { + this._stream_status = status; + } + + async getConfiguration(fd: number): Promise { + return DefaultDeviceConfiguration; + } + + async getOpenedDeviceList(): Promise | undefined> { + return undefined; + } +} diff --git a/packages/frontend-core/src/webusb/webUsbWsdr.ts b/packages/frontend-core/src/webusb/webUsbWsdr.ts new file mode 100644 index 0000000..0f846be --- /dev/null +++ b/packages/frontend-core/src/webusb/webUsbWsdr.ts @@ -0,0 +1,237 @@ +import { WebUsb, DeviceStreamType } from './webUsbBase'; +import type { + WebUsbParams, RXDecoderOptions, RXBuffer, DeviceConfiguration, TXBuffer, TXEncoderOptions +} from './webUsbBase'; +import { + DataType, CHUNK_SIZE, COMPLEX_FLOAT_SIZE, COMPLEX_INT16_SIZE +} from '@websdr/core/common'; + +const debug_usb_log = false; + +export abstract class WebUsbWsdr extends WebUsb { + static MAX_PACKET_SIZE = 0x000fffff; + static PACKET_ALIGN = 512 + static PACKET_ALIGN_MASK = WebUsbWsdr.PACKET_ALIGN - 1; + static MAX_PACKET_MASK = WebUsbWsdr.MAX_PACKET_SIZE - WebUsbWsdr.PACKET_ALIGN_MASK; + static HEADER_SIZE = 16; + static TRAILER_SIZE = 8; + static TRAILER_EXTRA_SIZE = 8; + static EXTRA_RX_DATA = 512 + 2048; + static MIN_SAMPLES_IN_PKT = 128; + static MAX_SAMPLES_IN_PKT = 8192; + + protected _timestamp: bigint = 0n; + + constructor(parms: WebUsbParams) { + super(parms) + } + + getRXSamplesCount(samples: number): number { + // any samples count in range [WebUsbWsdr.MIN_SAMPLES_IN_PKT;WebUsbWsdr.MAX_SAMPLES_IN_PKT] + return samples; + } + + getRXPacketSize(samples: number): number { + // Should be rounded up to 512b boundary and less than 1M, also added extra buffer for packet metadata + return ((samples * COMPLEX_INT16_SIZE + WebUsbWsdr.PACKET_ALIGN_MASK) & WebUsbWsdr.MAX_PACKET_MASK) + WebUsbWsdr.EXTRA_RX_DATA; + } + + getTXPacketSize(samples: number): number { + // Should be rounded up to 512b boundary and less than 1M, also added extra buffer for packet metadata + return (samples * COMPLEX_INT16_SIZE + WebUsbWsdr.HEADER_SIZE/* + WebUsbWsdr.PACKET_ALIGN_MASK */)/* & WebUsbWsdr.MAX_PACKET_MASK */; + } + + protected _fillHeader(buf: ArrayBufferLike, offset: number, discard_timestamp: boolean, timestamp: bigint, samples: number) { + // console.log('_fillHeader', /* buf, offset, discard_timestamp, */timestamp/* , samples */) + const discard_timestamp_flag = discard_timestamp ? 1 : 0; + /* + 16 bytes packet header + + DW0: [31:0] timestamp[31:0] + DW1: [31] discard_timestamp_flag + [30:16] samples_in_packet + [15:0] timestamp[47:32] + DW2: 00000000 + DW3: 00000000 + + DW4-N IQ 16 bit + */ + const bufLen = (buf as ArrayBufferLike).byteLength || 0; + if (bufLen - offset < WebUsbWsdr.HEADER_SIZE) + throw new Error(`WebUsbWsdr.fillHeader: Error: buffer slice must be at least ${WebUsbWsdr.HEADER_SIZE} bytes (offset ${offset})`); + if (samples < WebUsbWsdr.MIN_SAMPLES_IN_PKT || samples > WebUsbWsdr.MAX_SAMPLES_IN_PKT) + throw new Error(`WebUsbWsdr.fillHeader: Error: count of samples in the packet must be in range [${WebUsbWsdr.MIN_SAMPLES_IN_PKT}, ${WebUsbWsdr.MAX_SAMPLES_IN_PKT}], but set to ${samples} `) + + const view16 = new Uint16Array(buf, offset); + const view64 = new BigUint64Array(buf, offset); + view64[0] = timestamp & BigInt(0x0000ffffffffffff); //(timestamp >> BigInt(1)) & BigInt(0x0000ffffffffffff) + view64[1] = BigInt(0); + view16[3] = (discard_timestamp_flag << 15) + ((samples - 1) & 0x7fff); + // console.log('TX HEADER', view64[0].toString(16), view64[1].toString(16), buf.slice(0, 16)); + } + + async decodeRxData(data: DataView, samples: number, opts?: RXDecoderOptions): Promise { + if (globalThis.debug_mode || debug_usb_log) + console.log('RECEIVED DATA', data) + const id = opts !== undefined && opts.id !== undefined ? opts.id : -1; + const extraMeta = opts?.extra_meta === true; + const trailerSize = WebUsbWsdr.TRAILER_SIZE + (extraMeta ? WebUsbWsdr.TRAILER_EXTRA_SIZE : 0); + const dataSize = data.byteLength - trailerSize; + const samplesRecv = (dataSize / COMPLEX_INT16_SIZE) >> 0; + if (samples !== samplesRecv) { + return Promise.reject({ err: new Error(`WebUsbWsdr.parseRxData: Unexpected usb packet size (${samplesRecv} received !== ${samples} expected)`), id: opts?.id }); + } + const ts = data.getBigUint64(dataSize, true); + const overrun = Number(ts & BigInt(0x0000000000ffffff)); + let realigned = 0; + let dropped = 0; + if (extraMeta) { + const extra = data.getBigUint64(dataSize + WebUsbWsdr.TRAILER_SIZE, true); + realigned = Number(extra >> BigInt(64 - 20)) + dropped = Number(extra & BigInt(0xffff)) + } + + const datatype = opts?.datatype || DataType.ci16; + const elementSize = (datatype === DataType.cf32 || datatype === DataType.f32) ? COMPLEX_FLOAT_SIZE : COMPLEX_INT16_SIZE; + const output = opts?.data !== undefined ? opts.data : new SharedArrayBuffer(samplesRecv * elementSize); + WebUsb.fillData(datatype, output, 0, DataType.ci16, data.buffer, 0, (dataSize / Int16Array.BYTES_PER_ELEMENT) >> 0); + + this._timestamp += BigInt(overrun * samplesRecv); + + /*let output = undefined; + const datatype = opts?.datatype || DataType.ci16; + if (datatype === DataType.cf32 || datatype === DataType.f32) { + output = opts?.data !== undefined ? opts.data : new SharedArrayBuffer(samplesRecv * COMPLEX_FLOAT_SIZE); + const viewData = new Float32Array(output); + const view16 = new Int16Array(data.buffer, 0, (dataSize / Int16Array.BYTES_PER_ELEMENT) >> 0); + bufferI16ToF32(view16, viewData); + } else { + output = opts?.data !== undefined ? opts.data : new SharedArrayBuffer(samplesRecv * COMPLEX_INT16_SIZE); + const viewData = new Int16Array(output); + const view16 = new Int16Array(data.buffer, 0, (dataSize / Int16Array.BYTES_PER_ELEMENT) >> 0); + viewData.set(view16); + }*/ + // console.log('RECEIVED TIMESTAMP', this._timestamp); + if (globalThis.debug_mode || debug_usb_log) console.log('RECEIVE timestamp', this._timestamp, overrun, 'output', output.byteLength); + + const ret: RXBuffer = { + data: output, + datatype: datatype, + id: id, + samples: samplesRecv, + timestamp: this._timestamp, + overrun: overrun, + realigned: realigned, + dropped: dropped, + recvsize: data.byteLength, + } + this._timestamp += BigInt(samplesRecv); + + return ret; + } + + async encodeTxData(data: TXBuffer, opts?: TXEncoderOptions): Promise { + if (globalThis.debug_mode || debug_usb_log) + console.log('DATA TO SEND', data, data.datatype, data.byteOffset, data.byteLength); + // console.log('SEND TIMESTAMP', data.timestamp, data.byteLength); + // const viewin = new Int16Array(data.data, data.byteOffset, data.byteLength); + // console.log('VIEWIN', viewin.length, viewin); + // console.log('SEND TIMESTAMP', data.timestamp); + let output = undefined; + const samplesCnt = WebUsb.getSamplesCnt(data.datatype, data.byteLength || data.data.byteLength); + const sliceSamples = opts?.sliceSamples || samplesCnt; + const chunkCnt = Math.ceil(samplesCnt / sliceSamples); + const bufSize = samplesCnt * COMPLEX_INT16_SIZE + chunkCnt * WebUsbWsdr.HEADER_SIZE; /* this.getTXPacketSize(sampleCnt) */ + output = opts?.data !== undefined ? opts.data : new ArrayBuffer(bufSize); + if (output.byteLength < bufSize) + throw new Error(`WebUsbWsdr.encodeTxData: Error: buffer length must be more than ${bufSize} bytes`); + + let timestamp = data.timestamp; + const timestampInc = BigInt(sliceSamples); + const chunkSize = this.getTXPacketSize(sliceSamples); + const dataOffsetMain = data.byteOffset || 0; + const dataOffsetMult = sliceSamples * WebUsb.getSampleByteLength(data.datatype); + for (let i = 0; i < chunkCnt; ++i) { + const pktOffset = i * chunkSize + const dataOffset = dataOffsetMain + i * dataOffsetMult; + const samples = i < chunkCnt - 1 ? sliceSamples : samplesCnt - i * sliceSamples; + this._fillHeader(output, pktOffset, data.discard_timestamp, timestamp, samples); + WebUsb.fillData(DataType.ci16, output, pktOffset + WebUsbWsdr.HEADER_SIZE, data.datatype, data.data, dataOffset, samples << 1); + timestamp += timestampInc; + // console.log('OUTPUT', output); + } + + // const viewout = new Int16Array(output/* , WebUsbLimeSdr.HEADER_SIZE */); + // console.log('TIMESTAMP', data.timestamp, viewout.length, viewout /*, view.subarray(0, 10), view.subarray(view.length - 10, view.length) */); + return output; + } + + async open() { + await super.open(); + + if (this.module && this.device) { + try { + // if (!this.device.opened) await this.device.close(); + await this.device.open(); + try { + await this.device.reset(); //!!! this causes an error in windows + } catch (err) { + console.warn('Seems you use OS Windows and reset of usb device causes an error: ', err); + } + if (this.device.configuration === null) await this.device.selectConfiguration(1); + const ifaceNum = this.device.configuration?.interfaces[0]?.interfaceNumber ?? 0; + await this.device.claimInterface(ifaceNum); + // const res = await this.module._init_lib(this.fd, this.device.vendorId, this.device.productId); + const res = await this.module.ccall("init_lib", "number", ["number", "number", "number"], + [this.fd, this.device.vendorId, this.device.productId], { async: true } + ); + if (res < 0) this.device.close(); + } catch (err) { + console.error(err) + this.fd = -1; + } + } + } +} + +export class WebUsbXsdr extends WebUsbWsdr { + constructor(parms: WebUsbParams) { + super(parms) + if (globalThis.debug_mode || debug_usb_log) + console.log('Created WebUsbXsdr') + } + + getConfiguration(): DeviceConfiguration { + return { + defaultSamplesCount: CHUNK_SIZE, + rxFrequencyRange: { min: 30e6, max: 3800e6 }, + txFrequencyRange: { min: 30e6, max: 3800e6 }, + bandwidthRange: { min: 500e3, max: 65e6 }, + rateRange: { min: 192e3, max: 80e6 }, + streamTypes: { [DeviceStreamType.raw]: { dataTypes: [DataType.ci16] } }, + txRxDelay: 76, + warmupPackets: 0, + }; + } +} + +export class WebUsbUsdr extends WebUsbWsdr { + constructor(parms: WebUsbParams) { + super(parms) + if (globalThis.debug_mode || debug_usb_log) + console.log('Created WebUsbUsdr') + } + + getConfiguration(): DeviceConfiguration { + return { + defaultSamplesCount: CHUNK_SIZE, + rxFrequencyRange: { min: 100e3, max: 3600e6 }, + txFrequencyRange: { min: 300e6, max: 3600e6 }, + bandwidthRange: { min: 500e3, max: 40e6 }, + rateRange: { min: 1e6, max: 65e6 }, + streamTypes: { [DeviceStreamType.raw]: { dataTypes: [DataType.ci16] } }, + txRxDelay: 16, + warmupPackets: 0, + }; + } +} diff --git a/packages/frontend-core/tsconfig.json b/packages/frontend-core/tsconfig.json new file mode 100644 index 0000000..7f678df --- /dev/null +++ b/packages/frontend-core/tsconfig.json @@ -0,0 +1,56 @@ +{ + "compilerOptions": { + // File Layout + "rootDir": "src", + "outDir": "dist", + "baseUrl": ".", + "paths": { + "@/*": [ + "src/*" + ], + "vitest": [ + "../../node_modules/vitest" + ] + }, + // Environment Settings + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "types": [ + "vitest/globals", + "w3c-web-usb", + "emscripten", + "node" + ], + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + // "exactOptionalPropertyTypes": true, + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + // Recommended Options + "strict": true, + "esModuleInterop": true, + "jsx": "preserve", + "verbatimModuleSyntax": true, + // "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/packages/frontend-core/vitest.config.ts b/packages/frontend-core/vitest.config.ts new file mode 100644 index 0000000..b0a16d3 --- /dev/null +++ b/packages/frontend-core/vitest.config.ts @@ -0,0 +1,31 @@ +import { defineConfig } from 'vitest/config'; +import path from 'path'; + +export default defineConfig({ + test: { + name: 'frontend-core', + globals: true, + environment: 'node', + include: ['src/tests/**/*.test.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + exclude: [ + 'node_modules/**', + 'dist/**', + 'build/**', + 'old/**', + '**/tests/', + '*.config.*', + '**/index.ts', + '**/*.d.ts' + ], + reportsDirectory: './coverage' + }, + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + } +}); diff --git a/packages/nestjs-microservice/package.json b/packages/nestjs-microservice/package.json new file mode 100644 index 0000000..03dfa42 --- /dev/null +++ b/packages/nestjs-microservice/package.json @@ -0,0 +1,52 @@ +{ + "name": "@websdr/nestjs-microservice", + "version": "1.0.0", + "description": "This is a NestJS microservice for WebSDR", + "author": "Timur ", + "license": "MIT", + "keywords": [], + "main": "dist/index.js", + "types": "dist/index.d.ts", + "type": "module", + "scripts": { + "build": "tsc && tsc-alias && tsc-esm-fix", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" + }, + "dependencies": { + "@nestjs/common": "^11.1.8", + "@nestjs/config": "^4.0.2", + "@nestjs/jwt": "^11.0.1", + "@nestjs/microservices": "^11.1.8", + "@nestjs/passport": "^11.0.5", + "@nestjs/testing": "^11.1.8", + "@nestjs/websockets": "^11.1.8", + "@websdr/core": "^1.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.2", + "passport-jwt": "^4.0.1", + "ws": "^8.18.3" + }, + "devDependencies": { + "@types/passport-jwt": "^4.0.1", + "@types/ws": "^8.18.1" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.js" + }, + "./auth": { + "types": "./dist/auth/index.d.ts", + "import": "./dist/auth/index.js", + "require": "./dist/auth/index.js" + }, + "./users": { + "types": "./dist/users/index.d.ts", + "import": "./dist/users/index.js", + "require": "./dist/users/index.js" + } + } +} \ No newline at end of file diff --git a/packages/nestjs-microservice/src/auth/auth.controller.ts b/packages/nestjs-microservice/src/auth/auth.controller.ts new file mode 100644 index 0000000..858807e --- /dev/null +++ b/packages/nestjs-microservice/src/auth/auth.controller.ts @@ -0,0 +1,128 @@ +import { + Controller, Post, Body, Req, Res, UseGuards, Get, HttpException, + HttpStatus, UnauthorizedException, Logger +} from '@nestjs/common'; +import type { Response, CookieOptions } from 'express'; +import { AuthService } from '@/auth/auth.service'; +import { UsersService } from '@/users/users.service'; +import { LoginDto } from '@/auth/dto/login.dto'; +import { JwtAuthGuard } from '@/auth/jwt-auth.guard'; +import type { AuthRequest } from '@/auth/interfaces/auth-request.interface'; + +@Controller('api/auth') +export class AuthController { + protected readonly logger = new Logger(AuthController.name); + constructor(private readonly authService: AuthService, private readonly usersService: UsersService) { } + + @Post('login') + async login(@Body() dto: LoginDto, @Res() res: Response) { + this.logger?.debug('Login request received', dto.username); + const user = await this.authService.validateUser(dto.username, dto.password); + if (!user) throw new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED); + const token = await this.authService.createAccessToken({ + sub: user.id, + username: user.username, + name: user.name + }); + await this._setCookie(res, token); + if (!token) throw new HttpException('Auth service error', HttpStatus.INTERNAL_SERVER_ERROR); + this.logger?.debug('User logged in', user); + return res.json({ ok: true, message: 'User authenticated', user_id: user.id }); + } + + @UseGuards(JwtAuthGuard) + @Post('logout') + async logout(@Req() req: AuthRequest, @Res() res: Response) { + this.logger?.debug('Logout request received', req.user); + if (!req.user) throw new UnauthorizedException('Unauthorized'); + await this.authService.revokeToken(req.cookies?.jwt); + await this._setCookie(res, undefined); + return res.json({ ok: true, message: 'Logged out successfully' }); + } + + @Post('guest') + async guestLogin(@Req() req: AuthRequest, @Res() res: Response) { + this.logger?.debug('Guest login request received'); + // If already has a valid token, return the existing user info + let newToken: string | undefined, userId: string | number | undefined; + const token = req.cookies?.jwt; + let needNewToken = true; + if (token) { + // try to refresh the token + const payload = await this.authService.getTokenPayload(token); + if (payload?.sub) { + // valid token, refresh it + userId = payload.sub; + newToken = await this.authService.refreshToken(token); + if (newToken) needNewToken = false; + } + } + if (needNewToken) { + // Create a new guest user + userId = this.usersService.generateUserId(); + newToken = await this.authService.createAccessToken({ + sub: userId, + username: 'guest', + name: 'Guest User' + }, '1h'); + } + + await this._setCookie(res, newToken); + if (!newToken) throw new HttpException('Auth service error', HttpStatus.INTERNAL_SERVER_ERROR); + this.logger?.debug('Guest user logged in', userId); + return res.json({ ok: true, message: 'Guest authenticated', user_id: userId }); + } + + @UseGuards(JwtAuthGuard) + @Get('profile') + async getProfile(@Req() req: AuthRequest) { + this.logger?.debug('Profile request received', req.user); + if (!req.user) throw new UnauthorizedException('Unauthorized'); + return { ok: true, message: '', user: req.user }; + } + + @UseGuards(JwtAuthGuard) + @Post('refresh') + async refresh(@Req() req: AuthRequest, @Res() res: Response) { + this.logger?.debug('Refresh request received', req.user); + const token = req.cookies?.jwt; + if (!token) throw new HttpException('No token', HttpStatus.BAD_REQUEST); + const newToken = await this.authService.refreshToken(token); + + await this._setCookie(res, newToken); + if (!newToken) throw new HttpException('Invalid token', HttpStatus.UNAUTHORIZED); + this.logger?.debug('Token refreshed for user', req.user); + return res.json({ ok: true, message: 'Token refreshed' }); + } + + private async _setCookie(res: Response, token: string | undefined): Promise { + let maxAge = 60 * 60 * 1000; + let needClear = true; + + if (token) { + // Determine cookie maxAge from token expiry (exp). Fallback to 1h if not available. + const payload = await this.authService.getTokenPayload(token); + if (typeof payload?.exp === 'number') { + maxAge = payload.exp * 1000 - Date.now(); + needClear = maxAge <= 0; + } + } + + const cookieOptions: CookieOptions = { + httpOnly: true, // JS cannot read it + secure: process.env.NODE_ENV === 'production', // set true in production with HTTPS + sameSite: 'strict', // CSRF protection + path: '/', // ensure matches original cookie + }; + if (needClear) { + // Clear cookie reliably + res.clearCookie('jwt', cookieOptions); + } else { + // Set cookie + res.cookie('jwt', token, { + ...cookieOptions, + maxAge, // derived from token expiry + }); + } + } +} \ No newline at end of file diff --git a/packages/nestjs-microservice/src/auth/auth.module.ts b/packages/nestjs-microservice/src/auth/auth.module.ts new file mode 100644 index 0000000..cdc13d5 --- /dev/null +++ b/packages/nestjs-microservice/src/auth/auth.module.ts @@ -0,0 +1,32 @@ +import { Module } from '@nestjs/common'; +import { JwtModule } from '@nestjs/jwt'; +import { PassportModule } from '@nestjs/passport'; +import { ConfigModule } from '@nestjs/config'; +import { JwtAuthGuard } from '@/auth/jwt-auth.guard'; +import { JwtStrategy } from '@/auth/jwt.strategy'; +import { AuthService } from '@/auth/auth.service'; +import { AuthController } from '@/auth/auth.controller'; +import { UsersModule } from '@/users/users.module'; +import { JwtConfigModule, JWT_CONFIG } from '@/auth/jwt-config.module'; + +@Module({ + imports: [ + ConfigModule, + UsersModule, + JwtConfigModule, + PassportModule.register({ defaultStrategy: 'jwt' }), + JwtModule.registerAsync({ + imports: [JwtConfigModule], + inject: [JWT_CONFIG], + useFactory: (jwtConfig: { secret: string; signOptions?: any }) => jwtConfig, + }), + ], + controllers: [AuthController], + providers: [ + AuthService, + JwtStrategy, + JwtAuthGuard, + ], + exports: [AuthService], +}) +export class AuthModule { } \ No newline at end of file diff --git a/packages/nestjs-microservice/src/auth/auth.service.ts b/packages/nestjs-microservice/src/auth/auth.service.ts new file mode 100644 index 0000000..46f6c5c --- /dev/null +++ b/packages/nestjs-microservice/src/auth/auth.service.ts @@ -0,0 +1,78 @@ +import { Injectable } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { OmitPassword, UsersService } from '@/users/users.service'; +import type { User } from '@/users/users.service'; +import ms from 'ms'; + +@Injectable() +export class AuthService { + // Simple in-memory revoked token store: token -> expiryTimestamp(ms) + private revokedTokens = new Map(); + + constructor(private usersService: UsersService, private readonly jwtService: JwtService) { } + + @OmitPassword() + async validateUser(username: string, password: string): Promise { + const user = await this.usersService.findUser(username); + if (user && user.password === password) { + return user; + } + return undefined; + } + + async createAccessToken(payload: Record, expiresIn?: ms.StringValue | number) { + try { + const options = expiresIn ? { expiresIn } : undefined; + return this.jwtService.signAsync>(payload, options); + } catch { + return undefined; + } + } + + async getTokenPayload(token: string) { + try { + return await this.jwtService.verifyAsync>(token); + } catch { + return undefined; + } + } + + async refreshToken(token: string, expiresIn?: ms.StringValue | number) { + try { + const payload = await this.getTokenPayload(token); + if (!payload) return undefined; + const { iat, exp, ...rest } = payload as any; + return this.createAccessToken(rest, expiresIn); + } catch { + return undefined; + } + } + + /** + * Revoke a token until its expiry (or for 1 hour if exp missing). + */ + async revokeToken(token?: string | null): Promise { + if (!token) return; + try { + const payload = await this.getTokenPayload(token); + const exp = typeof payload?.exp === 'number' ? payload.exp * 1000 : undefined; + const until = exp ?? (Date.now() + 60 * 60 * 1000); + this.revokedTokens.set(token, until); + setTimeout(() => this.revokedTokens.delete(token), Math.max(0, until - Date.now())); + } catch { /* ignore */ } + } + + /** + * Check whether a token was revoked. Also auto-cleans expired entries. + */ + async isRevoked(token: string | null | undefined): Promise { + if (!token) return false; + const until = this.revokedTokens.get(token); + if (!until) return false; + if (Date.now() > until) { + this.revokedTokens.delete(token); + return false; + } + return true; + } +} \ No newline at end of file diff --git a/packages/nestjs-microservice/src/auth/dto/index.ts b/packages/nestjs-microservice/src/auth/dto/index.ts new file mode 100644 index 0000000..bce819a --- /dev/null +++ b/packages/nestjs-microservice/src/auth/dto/index.ts @@ -0,0 +1 @@ +export { LoginDto } from './login.dto'; diff --git a/packages/nestjs-microservice/src/auth/dto/login.dto.ts b/packages/nestjs-microservice/src/auth/dto/login.dto.ts new file mode 100644 index 0000000..9ba3967 --- /dev/null +++ b/packages/nestjs-microservice/src/auth/dto/login.dto.ts @@ -0,0 +1,11 @@ +import { IsString, IsNotEmpty } from 'class-validator'; + +export class LoginDto { + @IsString() + @IsNotEmpty() + username!: string; + + @IsString() + @IsNotEmpty() + password!: string; +} \ No newline at end of file diff --git a/packages/nestjs-microservice/src/auth/index.ts b/packages/nestjs-microservice/src/auth/index.ts new file mode 100644 index 0000000..c710f94 --- /dev/null +++ b/packages/nestjs-microservice/src/auth/index.ts @@ -0,0 +1,4 @@ +export { JwtAuthGuard } from './jwt-auth.guard'; +export { AuthModule } from './auth.module'; +export * from './interfaces'; +export * from './dto'; \ No newline at end of file diff --git a/packages/nestjs-microservice/src/auth/interfaces/auth-request.interface.ts b/packages/nestjs-microservice/src/auth/interfaces/auth-request.interface.ts new file mode 100644 index 0000000..ff29a6d --- /dev/null +++ b/packages/nestjs-microservice/src/auth/interfaces/auth-request.interface.ts @@ -0,0 +1,11 @@ +import type { Request } from 'express'; + +export interface AuthUser { + sub?: string; + [key: string]: any; +} + +export interface AuthRequest extends Request { + user?: AuthUser; + cookies: { [key: string]: any }; +} \ No newline at end of file diff --git a/packages/nestjs-microservice/src/auth/interfaces/index.ts b/packages/nestjs-microservice/src/auth/interfaces/index.ts new file mode 100644 index 0000000..9bb7095 --- /dev/null +++ b/packages/nestjs-microservice/src/auth/interfaces/index.ts @@ -0,0 +1,2 @@ +// Re-export all interfaces from a single entry point +export type { AuthUser, AuthRequest } from './auth-request.interface'; \ No newline at end of file diff --git a/packages/nestjs-microservice/src/auth/jwt-auth.guard.ts b/packages/nestjs-microservice/src/auth/jwt-auth.guard.ts new file mode 100644 index 0000000..18c0a8e --- /dev/null +++ b/packages/nestjs-microservice/src/auth/jwt-auth.guard.ts @@ -0,0 +1,38 @@ +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import type { ExecutionContext } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; +import { AuthService } from '@/auth/auth.service'; + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') { + constructor(private readonly authService: AuthService) { + super(); + } + + async canActivate(context: ExecutionContext) { + // run default JWT auth (validates signature, sets req.user) + const ok = (await super.canActivate(context)) as boolean; + if (!ok) return false; + + const req = context.switchToHttp().getRequest(); + const token = req.cookies?.jwt || (() => { + const a = req.headers?.authorization; + return typeof a === 'string' && a.startsWith('Bearer ') ? a.slice(7) : undefined; + })(); + + if (!token) throw new UnauthorizedException('No token'); + + const revoked = await this.authService.isRevoked(token); + if (revoked) throw new UnauthorizedException('Token revoked'); + + return true; + } + + // optional: preserve default error handling + handleRequest(err: any, user: any, info: any) { + if (err || !user) { + throw err || new UnauthorizedException(); + } + return user; + } +} \ No newline at end of file diff --git a/packages/nestjs-microservice/src/auth/jwt-config.module.ts b/packages/nestjs-microservice/src/auth/jwt-config.module.ts new file mode 100644 index 0000000..57d65f3 --- /dev/null +++ b/packages/nestjs-microservice/src/auth/jwt-config.module.ts @@ -0,0 +1,28 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; + +export const JWT_CONFIG = 'JWT_CONFIG'; + +@Module({ + imports: [ConfigModule], + providers: [ + { + provide: JWT_CONFIG, + inject: [ConfigService], + useFactory: (config: ConfigService) => { + const secret = config.get('JWT_SECRET') + || 'just_a_demo_secret_key_you_should_change_me'; + if (!secret) { + throw new Error('JWT_SECRET is not set'); + } + return { + secret, + algorithm: config.get('JWT_ALGORITHM') || 'HS256', + signOptions: { expiresIn: config.get('JWT_EXPIRES_IN') || '1h' }, + }; + }, + }, + ], + exports: [JWT_CONFIG], +}) +export class JwtConfigModule { } \ No newline at end of file diff --git a/packages/nestjs-microservice/src/auth/jwt.strategy.ts b/packages/nestjs-microservice/src/auth/jwt.strategy.ts new file mode 100644 index 0000000..ed7c012 --- /dev/null +++ b/packages/nestjs-microservice/src/auth/jwt.strategy.ts @@ -0,0 +1,34 @@ +import { Injectable, Inject, UnauthorizedException } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { Strategy, ExtractJwt } from 'passport-jwt'; +import { AuthService } from '@/auth/auth.service'; + +const JWT_CONFIG = 'JWT_CONFIG'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { + constructor( + @Inject(JWT_CONFIG) private readonly jwtConfig: { secret: string }, + private readonly authService: AuthService, + ) { + super({ + jwtFromRequest: ExtractJwt.fromExtractors([ + (req: any) => req?.cookies?.jwt ?? undefined, + ExtractJwt.fromAuthHeaderAsBearerToken(), + ]), + ignoreExpiration: false, + secretOrKey: jwtConfig.secret, + }); + } + + async validate(payload: any) { + if (!payload) { + throw new UnauthorizedException(); + } + // optional load user from UsersService: + // const user = await this.usersService.findSafeUserById(payload.sub); + // if (!user) throw new UnauthorizedException(); + // return user; + return { sub: payload.sub, username: payload.username, ...payload }; + } +} \ No newline at end of file diff --git a/packages/nestjs-microservice/src/common/index.ts b/packages/nestjs-microservice/src/common/index.ts new file mode 100644 index 0000000..7d481ec --- /dev/null +++ b/packages/nestjs-microservice/src/common/index.ts @@ -0,0 +1,4 @@ +// Re-export common functionalities +export { LoggerLevelService, parseLogLevels } from './logger-level.service'; +export type { LoggerLevels } from './logger-level.service'; +export { LoggingModule, LOGGER } from './logging.module'; \ No newline at end of file diff --git a/packages/nestjs-microservice/src/common/logger-level.service.ts b/packages/nestjs-microservice/src/common/logger-level.service.ts new file mode 100644 index 0000000..a2883c3 --- /dev/null +++ b/packages/nestjs-microservice/src/common/logger-level.service.ts @@ -0,0 +1,46 @@ +import { Injectable, Logger, LOG_LEVELS } from '@nestjs/common'; +import type { LogLevel } from '@nestjs/common'; + +export type { LogLevel }; + +export type LoggerLevels = LogLevel[] | false | undefined; + +export function parseLogLevels(raw?: string): LoggerLevels { + const s = (raw ?? '').toString().trim(); + if (s.length === 0) return undefined; + const lowered = s.toLowerCase(); + if (['false', 'off', 'none'].includes(lowered)) return false; + if (['true', 'on', 'all'].includes(lowered)) return LOG_LEVELS; + const parts = lowered.split(',').map((p) => p.trim()).filter(Boolean); + const allowed: LogLevel[] = LOG_LEVELS; + const invalid = parts.filter((p) => !allowed.includes(p as LogLevel)); + if (invalid.length > 0) console.warn(`Invalid LOG_LEVEL(S): ${invalid.join(',')}. Ignoring them.`); + const valid = parts.filter((p) => allowed.includes(p as LogLevel)); + return valid.length > 0 ? valid as LogLevel[] : undefined; +} + +@Injectable() +export class LoggerLevelService { + private current: LoggerLevels = undefined; + + getCurrent(): LoggerLevels { + return this.current; + } + + setLevels(levels: LoggerLevels) { + // Apply globally to Nest Logger + if (levels === this.current) return; + if (levels !== undefined) { + Logger.overrideLogger(levels); + // console.log('LoggerLevelService: applied levels -> ', levels); + } + this.current = levels; + } + + setLevelsFromString(raw?: string) { + const parsed = parseLogLevels(raw); + if (parsed !== null) this.setLevels(parsed as any); + } +} + +export default LoggerLevelService; diff --git a/packages/nestjs-microservice/src/common/logging.module.ts b/packages/nestjs-microservice/src/common/logging.module.ts new file mode 100644 index 0000000..290f567 --- /dev/null +++ b/packages/nestjs-microservice/src/common/logging.module.ts @@ -0,0 +1,45 @@ +import { Module, Global } from '@nestjs/common'; +import type { DynamicModule, LoggerService } from '@nestjs/common'; + +export const LOGGER = Symbol('LOGGER'); + +export function createContextLogger(base: any, context: string): LoggerService { + const call = (method: string, ...args: any[]) => { + const fn = base?.[method] ?? base?.log; + if (!fn) return; + if (method === 'error' || method === 'fatal') { + const [message, trace] = args; + fn.call(base, message, trace ?? undefined, context); + } else { + const [message] = args; + fn.call(base, message, context); + } + }; + + return { + log: (msg: any) => call('log', msg), + error: (msg: any, trace?: string) => call('error', msg, trace), + fatal: (msg: any, trace?: string) => call('fatal', msg, trace), + warn: (msg: any) => call('warn', msg), + debug: (msg: any) => call('debug', msg), + verbose: (msg: any) => call('verbose', msg), + }; +} + + +@Global() +@Module({}) +export class LoggingModule { + static forRoot(loggerInstance: unknown): DynamicModule { + return { + module: LoggingModule, + providers: [ + { + provide: LOGGER, + useValue: loggerInstance + } + ], + exports: [LOGGER], + }; + } +} \ No newline at end of file diff --git a/packages/nestjs-microservice/src/index.ts b/packages/nestjs-microservice/src/index.ts new file mode 100644 index 0000000..19665d2 --- /dev/null +++ b/packages/nestjs-microservice/src/index.ts @@ -0,0 +1,4 @@ +//Re-export all nestjs microservice functionalities +export * from './auth'; +export * from './common'; +export * from './users'; \ No newline at end of file diff --git a/packages/nestjs-microservice/src/tests/auth/auth.controller.test.ts b/packages/nestjs-microservice/src/tests/auth/auth.controller.test.ts new file mode 100644 index 0000000..d698fe4 --- /dev/null +++ b/packages/nestjs-microservice/src/tests/auth/auth.controller.test.ts @@ -0,0 +1,141 @@ +import { vi } from 'vitest'; +import { AuthController } from '@/auth/auth.controller'; +import { AuthService } from '@/auth/auth.service'; +import { UsersService } from '@/users/users.service'; +import { HttpException } from '@nestjs/common'; + +describe('AuthController', () => { + let controller: AuthController; + let mockAuth: Partial; + let mockUsers: Partial; + + beforeEach(() => { + const futureExp = Math.floor(Date.now() / 1000) + 60 * 60; // +1h + mockAuth = { + validateUser: vi.fn().mockImplementation((u: string, p: string) => { + if (u === 'admin' && p === 'password') return Promise.resolve({ id: 1, username: 'admin', name: 'Administrator' }); + return Promise.resolve(undefined); + }), + createAccessToken: vi.fn().mockResolvedValue('access-token'), + // Default payload has exp in future so _setCookie will set cookie + getTokenPayload: vi.fn().mockResolvedValue({ sub: '123', username: 'user', exp: futureExp }), + refreshToken: vi.fn().mockResolvedValue('new-token'), + revokeToken: vi.fn().mockResolvedValue(undefined), + }; + mockUsers = { + generateUserId: vi.fn().mockReturnValue('guest-123'), + }; + controller = new AuthController(mockAuth as AuthService, mockUsers as UsersService); + }); + + const makeRes = () => ({ + json: vi.fn().mockImplementation((v: any) => v), + cookie: vi.fn(), + clearCookie: vi.fn(), + } as any); + + it('login returns token on valid credentials', async () => { + const dto: any = { username: 'admin', password: 'password' }; + const resMock = makeRes(); + const res = await controller.login(dto, resMock); + expect(res).toEqual({ ok: true, message: 'User authenticated', user_id: 1 }); + expect((mockAuth.validateUser as any).mock.calls.length).toBe(1); + }); + + it('login throws on invalid credentials', async () => { + const dto: any = { username: 'admin', password: 'wrong' }; + const resMock = makeRes(); + await expect(controller.login(dto, resMock)).rejects.toThrow(HttpException); + }); + + it('login throws when auth service fails to create token', async () => { + (mockAuth.createAccessToken as any).mockResolvedValueOnce(undefined); + const dto: any = { username: 'admin', password: 'password' }; + const resMock = makeRes(); + await expect(controller.login(dto, resMock)).rejects.toThrow(HttpException); + }); + + it('getProfile returns req.user', async () => { + const req: any = { user: { sub: 1, username: 'admin' } }; + const res = await controller.getProfile(req); + expect(res).toEqual({ ok: true, message: '', user: req.user }); + }); + + it('refresh returns new token when cookie provided', async () => { + const req: any = { cookies: { jwt: 'token' } }; + const resMock = makeRes(); + const res = await controller.refresh(req, resMock); + expect(res).toEqual({ ok: true, message: 'Token refreshed' }); + expect((mockAuth.refreshToken as any).mock.calls.length).toBe(1); + }); + + it('refresh throws when no cookie token', async () => { + const req: any = { cookies: {} }; + const resMock = makeRes(); + await expect(controller.refresh(req, resMock)).rejects.toThrow(HttpException); + }); + + it('logout revokes token and clears cookie', async () => { + const req: any = { user: { sub: 1 }, cookies: { jwt: 'token' } }; + const resMock = makeRes(); + const result = await controller.logout(req, resMock); + expect(result).toEqual({ ok: true, message: 'Logged out successfully' }); + expect((mockAuth.revokeToken as any).mock.calls).toHaveLength(1); + expect((mockAuth.revokeToken as any).mock.calls[0][0]).toBe('token'); + expect(resMock.clearCookie).toHaveBeenCalledWith('jwt', expect.any(Object)); + }); + + it('guest creates a guest and sets cookie when no token', async () => { + const req: any = { cookies: {}, user: undefined }; + const resMock = makeRes(); + const result = await controller.guestLogin(req, resMock); + expect(resMock.cookie).toHaveBeenCalledWith('jwt', 'access-token', expect.objectContaining({ + httpOnly: true, + })); + expect(resMock.json).toHaveBeenCalledWith({ ok: true, message: 'Guest authenticated', user_id: 'guest-123' }); + }); + + it('guest refreshes token when token present and user attached', async () => { + const req: any = { cookies: { jwt: 'old-token' }, user: { sub: 'guest-123' } }; + const resMock = makeRes(); + (mockAuth.refreshToken as any).mockResolvedValueOnce('refreshed-token'); + const result = await controller.guestLogin(req, resMock); + expect((mockAuth.refreshToken as any).mock.calls.length).toBe(1); + expect(resMock.cookie).toHaveBeenCalledWith('jwt', 'refreshed-token', expect.any(Object)); + // controller uses token payload.sub (mock default is '123'), adjust expectation accordingly + expect(resMock.json).toHaveBeenCalledWith({ ok: true, message: 'Guest authenticated', user_id: '123' }); + }); + + it('guest falls back to create new guest when refresh fails', async () => { + const req: any = { cookies: { jwt: 'old-token' }, user: { sub: 'guest-123' } }; + const resMock = makeRes(); + (mockAuth.refreshToken as any).mockResolvedValueOnce(undefined); // refresh failed + const result = await controller.guestLogin(req, resMock); + // should create new guest token via createAccessToken (mocked as 'access-token') + expect(resMock.cookie).toHaveBeenCalledWith('jwt', 'access-token', expect.any(Object)); + expect(resMock.json).toHaveBeenCalledWith({ ok: true, message: 'Guest authenticated', user_id: 'guest-123' }); + }); + + it('guest with expired token refreshes and issues refreshed token', async () => { + const expired = Math.floor(Date.now() / 1000) - 10; + (mockAuth.getTokenPayload as any).mockResolvedValueOnce({ sub: 'guest-123', exp: expired }); + const req: any = { cookies: { jwt: 'expired-token' }, user: undefined }; + const resMock = makeRes(); + // default mockAuth.refreshToken returns 'new-token' + const result = await controller.guestLogin(req, resMock); + // expired token -> controller attempts refresh and sets refreshed token + expect(resMock.cookie).toHaveBeenCalledWith('jwt', 'new-token', expect.any(Object)); + expect(resMock.json).toHaveBeenCalledWith({ ok: true, message: 'Guest authenticated', user_id: 'guest-123' }); + }); + + it('guest with expired token and failed refresh clears cookie and issues new guest', async () => { + const expired = Math.floor(Date.now() / 1000) - 10; + (mockAuth.getTokenPayload as any).mockResolvedValueOnce({ sub: 'guest-123', exp: expired }); + (mockAuth.refreshToken as any).mockResolvedValueOnce(undefined); // refresh fails + const req: any = { cookies: { jwt: 'expired-token' }, user: undefined }; + const resMock = makeRes(); + const result = await controller.guestLogin(req, resMock); + expect(resMock.cookie).toHaveBeenCalledWith('jwt', 'access-token', expect.any(Object)); + expect(resMock.json).toHaveBeenCalledWith({ ok: true, message: 'Guest authenticated', user_id: 'guest-123' }); + }); +}); \ No newline at end of file diff --git a/packages/nestjs-microservice/src/tests/auth/auth.service.test.ts b/packages/nestjs-microservice/src/tests/auth/auth.service.test.ts new file mode 100644 index 0000000..5c0c134 --- /dev/null +++ b/packages/nestjs-microservice/src/tests/auth/auth.service.test.ts @@ -0,0 +1,56 @@ +import { vi } from 'vitest'; +import { UsersService } from '@/users/users.service'; +import { AuthService } from '@/auth/auth.service'; +import { JwtService } from '@nestjs/jwt'; + +describe('AuthService', () => { + let service: AuthService; + let mockUsers: Partial; + let mockJwt: Partial; + + beforeEach(() => { + mockUsers = { + findUser: vi.fn().mockImplementation((username: string) => { + if (username === 'admin') { + return Promise.resolve({ id: 1, username: 'admin', password: 'password' }); + } + return Promise.resolve(undefined); + }), + }; + mockJwt = { + signAsync: vi.fn().mockResolvedValue('signed-token'), + verifyAsync: vi.fn().mockResolvedValue({ sub: 1, username: 'tester' }), + }; + service = new AuthService(mockUsers as UsersService, mockJwt as JwtService); + }); + + it('validateUser returns safe user without password', async () => { + const user = await service.validateUser('admin', 'password'); + expect(user).toBeDefined(); + expect((user as any).password).toBeUndefined(); + expect((user as any).username).toBe('admin'); + }); + + it('validateUser returns undefined for wrong credentials', async () => { + const user = await service.validateUser('admin', 'bad'); + expect(user).toBeUndefined(); + }); + + it('createAccessToken calls JwtService.signAsync and returns token', async () => { + const token = await service.createAccessToken({ foo: 'bar' }, '2h'); + expect(token).toBe('signed-token'); + expect((mockJwt.signAsync as any).mock.calls.length).toBeGreaterThan(0); + }); + + it('refreshToken returns new token for valid token', async () => { + const token = await service.refreshToken('valid-token'); + expect(token).toBe('signed-token'); + expect((mockJwt.verifyAsync as any).mock.calls.length).toBeGreaterThan(0); + }); + + it('refreshToken returns undefined for invalid token', async () => { + (mockJwt.verifyAsync as any).mockRejectedValueOnce(new Error('invalid')); + const token = await service.refreshToken('bad-token'); + expect(token).toBeUndefined(); + }); +}); \ No newline at end of file diff --git a/packages/nestjs-microservice/src/tests/auth/jwt-auth.guard.test.ts b/packages/nestjs-microservice/src/tests/auth/jwt-auth.guard.test.ts new file mode 100644 index 0000000..1f91192 --- /dev/null +++ b/packages/nestjs-microservice/src/tests/auth/jwt-auth.guard.test.ts @@ -0,0 +1,156 @@ +import { JwtAuthGuard } from '@/auth/jwt-auth.guard'; +import { describe, it, expect, vi, afterEach } from 'vitest'; +import type { ExecutionContext } from '@nestjs/common'; +import { UnauthorizedException } from '@nestjs/common'; + +// Mock the AuthService module to avoid loading real implementation via path alias +vi.mock('@/auth/auth.service', () => { + class AuthService { } + return { AuthService }; +}); + + +describe('JwtAuthGuard constructor', () => { + it('should create an instance', () => { + const mockAuthService = { isRevoked: vi.fn() }; + const guard = new JwtAuthGuard(mockAuthService as any); + expect(guard).toBeInstanceOf(JwtAuthGuard); + }); + + it('should assign provided AuthService to instance', () => { + const mockAuthService = { isRevoked: vi.fn() }; + const guard = new JwtAuthGuard(mockAuthService as any); + expect((guard as any).authService).toBe(mockAuthService); + }); + + // Mock the AuthService module to avoid loading real implementation via path alias + vi.mock('@/auth/auth.service', () => { + class AuthService { } + return { AuthService }; + }); + + const makeContext = (req: any): ExecutionContext => + ({ + switchToHttp: () => ({ + getRequest: () => req, + }), + } as any); + + describe('JwtAuthGuard canActivate', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('returns false when super.canActivate returns false', async () => { + const mockAuthService = { isRevoked: vi.fn() }; + const guard = new JwtAuthGuard(mockAuthService as any); + + const parentProto = Object.getPrototypeOf(JwtAuthGuard.prototype); + const superSpy = vi + .spyOn(parentProto, 'canActivate') + .mockResolvedValue(false as any); + + const req = { cookies: {}, headers: {} }; + const ctx = makeContext(req); + + const result = await guard.canActivate(ctx); + expect(result).toBe(false); + expect(superSpy).toHaveBeenCalledTimes(1); + expect(mockAuthService.isRevoked).not.toHaveBeenCalled(); + }); + + it('throws UnauthorizedException with "No token" when token missing', async () => { + const mockAuthService = { isRevoked: vi.fn() }; + const guard = new JwtAuthGuard(mockAuthService as any); + + const parentProto = Object.getPrototypeOf(JwtAuthGuard.prototype); + vi.spyOn(parentProto, 'canActivate').mockResolvedValue(true as any); + + const req = { cookies: {}, headers: {} }; + const ctx = makeContext(req); + + await expect(guard.canActivate(ctx)).rejects.toBeInstanceOf(UnauthorizedException); + try { + await guard.canActivate(ctx); + } catch (e: any) { + const res = typeof e.getResponse === 'function' ? e.getResponse() : undefined; + if (res && typeof res === 'object') { + expect((res as any).message).toBe('No token'); + } + } + expect(mockAuthService.isRevoked).not.toHaveBeenCalled(); + }); + + it('uses token from cookies and returns true when not revoked', async () => { + const mockAuthService = { isRevoked: vi.fn().mockResolvedValue(false) }; + const guard = new JwtAuthGuard(mockAuthService as any); + + const parentProto = Object.getPrototypeOf(JwtAuthGuard.prototype); + vi.spyOn(parentProto, 'canActivate').mockResolvedValue(true as any); + + const token = 'cookie-token'; + const req = { cookies: { jwt: token }, headers: {} }; + const ctx = makeContext(req); + + const result = await guard.canActivate(ctx); + expect(result).toBe(true); + expect(mockAuthService.isRevoked).toHaveBeenCalledWith(token); + }); + + it('uses token from Authorization header and throws when revoked', async () => { + const mockAuthService = { isRevoked: vi.fn().mockResolvedValue(true) }; + const guard = new JwtAuthGuard(mockAuthService as any); + + const parentProto = Object.getPrototypeOf(JwtAuthGuard.prototype); + vi.spyOn(parentProto, 'canActivate').mockResolvedValue(true as any); + + const token = 'header-token'; + const req = { cookies: {}, headers: { authorization: `Bearer ${token}` } }; + const ctx = makeContext(req); + + await expect(guard.canActivate(ctx)).rejects.toBeInstanceOf(UnauthorizedException); + try { + await guard.canActivate(ctx); + } catch (e: any) { + const res = typeof e.getResponse === 'function' ? e.getResponse() : undefined; + if (res && typeof res === 'object') { + expect((res as any).message).toBe('Token revoked'); + } + } + expect(mockAuthService.isRevoked).toHaveBeenCalledWith(token); + }); + + it('treats non-Bearer Authorization header as missing token', async () => { + const mockAuthService = { isRevoked: vi.fn() }; + const guard = new JwtAuthGuard(mockAuthService as any); + + const parentProto = Object.getPrototypeOf(JwtAuthGuard.prototype); + vi.spyOn(parentProto, 'canActivate').mockResolvedValue(true as any); + + const req = { cookies: {}, headers: { authorization: 'Basic xyz' } }; + const ctx = makeContext(req); + + await expect(guard.canActivate(ctx)).rejects.toBeInstanceOf(UnauthorizedException); + expect(mockAuthService.isRevoked).not.toHaveBeenCalled(); + }); + }); + + describe('JwtAuthGuard handleRequest', () => { + it('returns user when no error and user present', () => { + const guard = new JwtAuthGuard({ isRevoked: vi.fn() } as any); + const user = { id: 1 }; + expect(guard.handleRequest(null, user, null)).toBe(user); + }); + + it('throws provided error when error present', () => { + const guard = new JwtAuthGuard({ isRevoked: vi.fn() } as any); + const err = new Error('boom'); + expect(() => guard.handleRequest(err, null, null)).toThrow(err); + }); + + it('throws UnauthorizedException when no user and no error', () => { + const guard = new JwtAuthGuard({ isRevoked: vi.fn() } as any); + expect(() => guard.handleRequest(null, null, null)).toThrow(UnauthorizedException); + }); + }); +}); \ No newline at end of file diff --git a/packages/nestjs-microservice/src/tests/auth/jwt.strategy.test.ts b/packages/nestjs-microservice/src/tests/auth/jwt.strategy.test.ts new file mode 100644 index 0000000..b034cc1 --- /dev/null +++ b/packages/nestjs-microservice/src/tests/auth/jwt.strategy.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { Test, TestingModule } from '@nestjs/testing'; +import { JwtStrategy } from '@/auth/jwt.strategy'; +import { AuthService } from '@/auth/auth.service'; + +const JWT_CONFIG = 'JWT_CONFIG'; + +describe('JwtStrategy', () => { + let strategy: JwtStrategy; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + JwtStrategy, + { + provide: JWT_CONFIG, + useValue: { secret: 'test_secret', signOptions: { expiresIn: '1h' } }, + }, + { + provide: AuthService, + useValue: {}, + }, + ], + }).compile(); + + strategy = module.get(JwtStrategy); + }); + + it('validate returns user-like object built from payload', async () => { + const payload = { sub: '123', username: 'user', foo: 'bar' }; + const result = await strategy.validate(payload); + expect(result).toMatchObject({ sub: '123', username: 'user' }); + }); +}); \ No newline at end of file diff --git a/packages/nestjs-microservice/src/tests/users/users.service.test.ts b/packages/nestjs-microservice/src/tests/users/users.service.test.ts new file mode 100644 index 0000000..b963dfd --- /dev/null +++ b/packages/nestjs-microservice/src/tests/users/users.service.test.ts @@ -0,0 +1,120 @@ +import { describe, it, expect } from 'vitest'; +import { UsersService, OmitPassword } from '@/users/users.service'; +import type { User } from '@/users/users.service'; + +describe('UsersService', () => { + const service = new UsersService(); + + it('findUser returns full user with password', async () => { + const user = await service.findUser('admin'); + expect(user).toMatchObject({ username: 'admin', password: 'admin' }); + }); + + it('findSafeUser omits password', async () => { + const user = await service.findSafeUser('admin'); + expect(user).toMatchObject({ username: 'admin', name: 'Administrator' }); + // @ts-expect-error password should be omitted + expect(user.password).toBeUndefined(); + }); + + it('findUserById returns full user', async () => { + const user = await service.findUserById(2); + expect(user).toMatchObject({ id: 2, username: 'user', password: 'user' }); + }); + + it('findSafeUserById omits password', async () => { + const user = await service.findSafeUserById(2); + expect(user).toMatchObject({ id: 2, username: 'user', name: 'User' }); + // @ts-expect-error password should be omitted + expect(user.password).toBeUndefined(); + }); + + it('findSafeUser returns undefined for unknown username', async () => { + const user = await service.findSafeUser('nope'); + expect(user).toBeUndefined(); + }); + + it('generateUserId returns valid UUID v4', () => { + const id = service.generateUserId(); + const uuidV4Regex = + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + expect(id).toMatch(uuidV4Regex); + }); +}); + +describe('OmitPassword decorator (standalone behavior)', () => { + class TestService { + private data: User[] = [ + { id: 1, username: 'a', password: 'pa', name: 'A' }, + { id: 2, username: 'b', password: 'pb', name: 'B' }, + ]; + + @OmitPassword() + getOneSync(): User { + return this.data[0]!; + } + + @OmitPassword() + getOneAsync(): Promise { + return Promise.resolve(this.data[1]!); + } + + @OmitPassword() + getArray(): User[] { + return this.data; + } + + @OmitPassword() + getArrayAsync(): Promise { + return Promise.resolve(this.data); + } + + @OmitPassword() + getNull(): User | null { + return null; + } + + @OmitPassword() + throws(): User { + throw new Error('Failure'); + } + } + + const ts = new TestService(); + + it('removes password from sync single user', () => { + const u = ts.getOneSync(); + expect(u.password).toBeUndefined(); + expect(u.username).toBe('a'); + }); + + it('removes password from async single user', async () => { + const u = await ts.getOneAsync(); + expect(u.password).toBeUndefined(); + expect(u.username).toBe('b'); + }); + + it('removes password from array', () => { + const arr = ts.getArray(); + expect(arr).toHaveLength(2); + arr.forEach(u => { + expect(u.password).toBeUndefined(); + }); + }); + + it('removes password from async array', async () => { + const arr = await ts.getArrayAsync(); + expect(arr).toHaveLength(2); + arr.forEach(u => { + expect(u.password).toBeUndefined(); + }); + }); + + it('returns null unchanged', () => { + expect(ts.getNull()).toBeNull(); + }); + + it('re-throws errors', () => { + expect(() => ts.throws()).toThrow('Failure'); + }); +}); \ No newline at end of file diff --git a/packages/nestjs-microservice/src/users/index.ts b/packages/nestjs-microservice/src/users/index.ts new file mode 100644 index 0000000..dde8afc --- /dev/null +++ b/packages/nestjs-microservice/src/users/index.ts @@ -0,0 +1,2 @@ +// Re-export users functionalities +export { UsersModule } from './users.module'; \ No newline at end of file diff --git a/packages/nestjs-microservice/src/users/users.module.ts b/packages/nestjs-microservice/src/users/users.module.ts new file mode 100644 index 0000000..badb68e --- /dev/null +++ b/packages/nestjs-microservice/src/users/users.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { UsersService } from './users.service'; + +@Module({ + providers: [UsersService], + exports: [UsersService], +}) +export class UsersModule { } diff --git a/packages/nestjs-microservice/src/users/users.service.ts b/packages/nestjs-microservice/src/users/users.service.ts new file mode 100644 index 0000000..a46987e --- /dev/null +++ b/packages/nestjs-microservice/src/users/users.service.ts @@ -0,0 +1,84 @@ +import { Injectable } from '@nestjs/common'; +import { randomUUID } from 'crypto'; + +// User entity and SafeUser type +export interface User { + id: number; + username: string; + password: string; + name?: string; +} + +export type SafeUser = Omit; + +// Method decorator to omit password field from returned User or User[] objects +export function OmitPassword() { + return function ( + _target: any, + _propertyKey: string, + descriptor: PropertyDescriptor, + ) { + const original = descriptor.value; + descriptor.value = function (...args: any[]) { + const handle = (value: any) => { + if (value == null) return value; + const strip = (v: any) => { + if (v && typeof v === 'object') { + const { password, ...rest } = v; + return rest; + } + return v; + }; + if (Array.isArray(value)) return value.map(strip); + return strip(value); + }; + + try { + const result = original.apply(this, args); + if (result && typeof result.then === 'function') { + return result.then(handle); + } + return handle(result); + } catch (err) { + throw err; + } + }; + return descriptor; + }; +} + +@Injectable() +export class UsersService { + // Demo users (in real app you should use a real module with DB) + private readonly users: User[] = [ + { id: 1, username: 'admin', password: 'admin', name: 'Administrator' }, + { id: 2, username: 'user', password: 'user', name: 'User' }, + ]; + + // Find user by username + async findUser(username: string): Promise { + return this.users.find(user => user.username === username); + } + + // Find user by username, omitting password field + @OmitPassword() + async findSafeUser(username: string): Promise { + return this.findUser(username); + } + + // Find user by Id + async findUserById(id: number): Promise { + return this.users.find(user => user.id === id); + } + + // Find user by Id, omitting password field + @OmitPassword() + async findSafeUserById(id: number): Promise { + return this.findUserById(id); + } + + // Generate a new random user ID (UUID v4) + generateUserId(): string { + return randomUUID(); + } +} diff --git a/packages/nestjs-microservice/tsconfig.json b/packages/nestjs-microservice/tsconfig.json new file mode 100644 index 0000000..fe7743f --- /dev/null +++ b/packages/nestjs-microservice/tsconfig.json @@ -0,0 +1,55 @@ +{ + "compilerOptions": { + // File Layout + "rootDir": "src", + "outDir": "dist", + "baseUrl": ".", + "paths": { + "@/*": [ + "src/*" + ], + "vitest": [ + "../../node_modules/vitest" + ] + }, + // Environment Settings + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "types": [ + "vitest/globals" + ], + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + // "exactOptionalPropertyTypes": true, + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + // Recommended Options + "strict": true, + "esModuleInterop": true, + "jsx": "preserve", + "verbatimModuleSyntax": true, + // "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": [ + "src/**/*", + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/packages/nestjs-microservice/vitest.config.ts b/packages/nestjs-microservice/vitest.config.ts new file mode 100644 index 0000000..907829d --- /dev/null +++ b/packages/nestjs-microservice/vitest.config.ts @@ -0,0 +1,31 @@ +import { defineConfig } from 'vitest/config'; +import path from 'path'; + +export default defineConfig({ + test: { + name: 'nestjs-microservice', + globals: true, + environment: 'node', + include: ['src/tests/**/*.test.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + exclude: [ + 'node_modules/**', + 'dist/**', + 'build/**', + 'old/**', + '**/tests/', + '*.config.*', + '**/index.ts', + '**/*.d.ts' + ], + reportsDirectory: './coverage' + }, + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + } +}); diff --git a/packages/vue3-components/README.md b/packages/vue3-components/README.md new file mode 100644 index 0000000..a16bd3e --- /dev/null +++ b/packages/vue3-components/README.md @@ -0,0 +1,609 @@ +# Vue 3 Components + +A collection of Vue 3 components with customizable styling using CSS variables. + +## Installation + +```bash +npm install @websdr/vue3-components +``` + +## Components + +### Dropdown +**Usage:** +```vue + +``` + +### List + +A performant, virtualized list component with optional auto-scroll behavior. `List` renders only visible rows into the DOM and uses a spacer + translated window to provide a full scrollable experience even for very large datasets. + +Key features: +- Supports both in-memory arrays (`items`) and on-demand access via `getItem` + `totalCount` for very large lists. +- Optional fixed `itemHeight` or automatic measurement when `itemHeight` is `0` (uses `ResizeObserver` and averages several visible rows). +- Virtual buffer above/below the viewport to reduce popping during scrolls. +- Optional `autoScroll` that keeps the view pinned to the bottom (useful for logs/chat) and pauses when the user scrolls away. +- Simple slot-based rendering: the default slot receives `{ item, index }` for each visible row. + +**Usage (in-memory items):** +```vue + + + +``` + +**Usage (on-demand getter for huge lists):** +```vue + + + +``` + +**Props:** +- `items?: Array` — optional full array source (when present `getItem` / `totalCount` are ignored). +- `totalCount?: number` — optional total count when using `getItem` (required for on-demand access). +- `getItem?: (index: number) => T` — optional synchronous getter used to access items on-demand. +- `itemHeight?: number` — height in px of each row. `0` means auto-measure (component will measure rendered items). If unset or `0`, fallback/initial estimate is 25px until measured. +- `virtualBuffer?: number` — rows above/below viewport to render as buffer (default: 5). +- `maxHeight?: string` — maximum height for the list (e.g. `"200px"` or `"50%"`) (default: `"100%"`). +- `class?: string` — additional class for the root container. +- `style?: Record` — additional inline styles for the root container. +- `autoScroll?: boolean` — when `true`, the list will auto-scroll to the bottom when new items are appended (default: `false`). Auto-scroll pauses while the user scrolls away from the bottom, and resumes when scrolled back. + +**Slot:** +- Default slot receives `{ item, index }` for each visible row. Use this to render each item. + +**Behavior / Notes:** +- The component places a transparent spacer element with the full `totalHeight` so native scrolling works as if all rows are rendered. +- Visible rows are rendered inside an absolutely-positioned window translated by `translateY(offset)`. +- If `itemHeight` is `0` (auto-measure), `ResizeObserver` measures several visible children (up to 8) and uses the average as the measured row height. +- When using `getItem`, provide a correct `totalCount` and a synchronous `getItem` function — the component expects immediate values, not promises. +- `autoScroll` will only scroll programmatically when new items increase `totalCount`, and it respects a small bottom threshold to avoid jumps. +- There are no custom events emitted by the component; interaction is done via props/slot and standard DOM scroll behavior. + +### LogArea + +A log viewer component with filtering capabilities, virtual scrolling, and real-time updates. + +**Features:** +- Virtual scrolling for performance with large datasets +- Filter by log level (fatal, error, warning, info, debug) +- Filter by subsystem +- Search functionality +- Clear logs action +- Dark theme support + +**Usage:** +```vue + + + +``` + +**Props:** +- `logs` (Array): Array of log objects with `id`, `timestamp`, `level`, `subsystem`, `message` +- `subsystems` (Array): Available subsystems for filtering +- `itemHeight` (Number): Height of each log item in pixels (default: 24) +- `bufferSize` (Number): Number of items to render outside visible area (default: 10) + +**Events:** +- `clear-logs`: Emitted when clear button is clicked + +### SdrInput + +A lightweight SDR (Software Defined Radio) selector with an input-like appearance. The component wraps a `Dropdown` trigger and uses the WebUSB manager to request/select SDR devices. + +Key points: +- Shows the currently selected device name (or a placeholder when none is selected). +- Opens the WebUSB device picker when the dropdown is opened. +- Emits an update event with the selected device info so the parent can react. + +**Usage:** +```vue + + + +``` + +**Props:** +- `device` (RequestDeviceInfo | undefined) — Currently selected SDR device. Shape: `{ devName: string, vendorId: number, productId: number }`. +- `mode?: 'single' | 'worker'` — WebUsb manager mode. Defaults to `'single'`. +- `placeholder?: string` — Placeholder text when no device is selected. Defaults to `'Click to select SDR'`. +- `size?: DropdownProps['size']` — Visual size of the input (passes through to `Dropdown`). Defaults to `'medium'`. +- `disabled?: boolean` — When true the input is disabled. Defaults to `false`. + +**Emits:** +- `update::device` (RequestDeviceInfo | undefined) — Emitted when the selected device changes. Use `v-model:device` to bind. + +**Behavior / Notes:** +- When opened the component calls the WebUSB manager (`getWebUsbManagerInstance`) to request a device. +- If a device is returned it becomes the selected device and `update::device` is emitted with the `RequestDeviceInfo`. +- If the picker is cancelled the component emits an empty device object (fields set to empty/0) to indicate no selection. +- The visual status (error/success) is derived from whether a device name is present. + +**Defaults** +- `mode`: `'single'` +- `placeholder`: `'Click to select SDR'` +- `disabled`: `false` +- `size`: `'medium'` + +## Styling + +All components are designed for maximum customization using CSS custom properties (variables). This allows you to easily integrate them with your existing design system or CSS framework. + +### Quick Start + +Import the base variables to get started: + +```scss +@import '@websdr/vue3-components/src/styles/variables.scss'; +``` + +### Component Customization + +Each component exposes specific CSS variables for granular control: + +#### LogArea Component Variables +```scss +:root { + /* Log level colors (customizable) */ + --log-fatal-color: darkred; + --log-error-color: red; + --log-warning-color: rgb(190, 124, 0); + --log-info-color: black; + --log-debug-color: blue; + + /* Layout variables (customizable) */ + --logarea-filter-bg: var(--light, #f8f9fa); + --logarea-filter-padding: var(--input-padding, 12px); + --logarea-filter-gap: 1rem; + --logarea-item-padding: 0.5rem; + + /* Typography variables (customizable) */ + --logarea-item-font-family: ui-monospace, SFMono-Regular, Monaco, 'Courier New', monospace; + --logarea-item-font-size: var(--input-font-size, 14px); + --logarea-item-line-height: 1rem; +} +``` + +#### Dropdown Component Variables +```scss +:root { + /* Container variables (customizable) */ + --dropdown-bg: var(--white, #ffffff); + --dropdown-border: var(--input-border, 1px solid #d1d5db); + --dropdown-border-radius: var(--input-border-radius, 6px); + --dropdown-shadow: var(--shadow, 0 10px 15px -3px rgba(0, 0, 0, 0.1)); + --dropdown-padding: var(--input-padding, 8px 12px); + + /* State variables (customizable) */ + --dropdown-focus-border: var(--input-focus-border, #3b82f6); + --dropdown-focus-shadow: var(--input-focus-shadow, 0 0 0 3px rgba(59, 130, 246, 0.1)); + + /* Option variables (customizable) */ + --dropdown-item-hover-bg: var(--light, #f3f4f6); + --dropdown-item-active-bg: var(--primary-color, #3b82f6); + --dropdown-option-selected-bg: var(--primary-light, #eff6ff); + --dropdown-option-selected-text: var(--primary-color, #3b82f6); +} +``` + +#### SdrInput Component Variables +```scss +:root { + /* Uses base variables from variables.scss */ + /* Status indicator colors */ + --success-color: #28a745; /* Connected state */ + --danger-color: #dc3545; /* Disconnected state */ + --primary-color: #007bff; /* Configured state */ + --muted: #6c757d; /* Empty state text */ +} +``` + +### Base Variables + +Core design tokens available for all components: + +```scss +:root { + /* === PRIMARY COLOR PALETTE === */ + --primary-color: #007bff; + --primary-hover: #0056b3; + --primary-light: #cce7ff; + --secondary-color: #6c757d; + --secondary-hover: #545b62; + --success-color: #28a745; + --success-hover: #218838; + --warning-color: #ffc107; + --warning-hover: #e0a800; + --danger-color: #dc3545; + --danger-hover: #c82333; + --info-color: #17a2b8; + + /* === NEUTRAL COLORS === */ + --white: #ffffff; + --light: #f8f9fa; + --dark: #495057; + --muted: #6c757d; + --border: #ced4da; + --border-light: #dee2e6; + --border-dark: #495057; + --disabled-bg: #e9ecef; + --text-muted: #6c757d; + --text-light: #9ca3af; + + /* === FORM ELEMENT VARIABLES === */ + --input-padding: 12px; + --input-border-radius: 6px; + --input-font-size: 14px; + --input-border: 1px solid var(--border); + --input-focus-border: #80bdff; + --input-focus-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + --input-transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + + /* === BUTTON VARIABLES === */ + --button-padding: 12px 24px; + --button-border-radius: 6px; + --button-font-size: 14px; + --button-font-weight: 600; + --button-transition: background-color 0.15s ease-in-out; + + /* === SHADOW VARIABLES === */ + --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1); + --shadow-md: 0 2px 10px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.3); + --shadow: var(--shadow-md); + + /* === Z-INDEX LEVELS === */ + --z-dropdown: 1000; + --z-modal: 1050; + + /* === RESPONSIVE BREAKPOINTS === */ + --mobile-breakpoint: 768px; + + /* === ANIMATION TIMING === */ + --transition-fast: 0.15s ease-in-out; + --transition-normal: 0.2s ease; + --transition-slow: 0.3s ease; +} +``` + +### Dark Theme Support + +Enable dark theme by adding `data-theme="dark"` attribute or `theme-dark` class: + +```html +
+ + +
+``` + +Dark theme automatically overrides variables: + +```scss +[data-theme="dark"], +.theme-dark { + --white: #1f232b; + --light: #2a2f3a; + --dark: #f8f9fa; + --muted: #9aa4b2; + --border: #3a4149; + --border-light: #2a2f3a; + + /* Component-specific dark theme overrides */ + --log-fatal-color: rgb(201, 0, 0); + --log-error-color: rgb(255, 78, 78); + --log-warning-color: rgb(238, 155, 0); + --log-info-color: lightgray; + --log-debug-color: rgb(0, 191, 255); + + --dropdown-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5); +} +``` + +### Framework Integration Examples + +#### Integration with Bulma CSS +```scss +:root { + /* Use Bulma's color system */ + --primary-color: hsl(171, 100%, 41%); + --primary-hover: hsl(171, 100%, 35%); + --danger-color: hsl(348, 86%, 61%); + --success-color: hsl(141, 53%, 53%); + --warning-color: hsl(48, 100%, 67%); + + /* Use Bulma's sizing */ + --input-font-size: 1rem; + --button-font-size: 1rem; + --input-border-radius: 4px; +} +``` + +#### Integration with Tailwind CSS +```scss +:root { + /* Use Tailwind color palette */ + --primary-color: theme('colors.blue.500'); + --primary-hover: theme('colors.blue.600'); + --success-color: theme('colors.green.500'); + --warning-color: theme('colors.yellow.500'); + --danger-color: theme('colors.red.500'); + + /* Use Tailwind spacing and border radius */ + --input-padding: theme('spacing.3'); + --button-padding: theme('spacing.2') theme('spacing.4'); + --input-border-radius: theme('borderRadius.md'); +} +``` + +### Advanced Customization Examples + +#### Custom Log Colors and Typography +```scss +:root { + /* Custom monospace font for logs */ + --logarea-item-font-family: 'Fira Code', 'JetBrains Mono', monospace; + --logarea-item-font-size: 13px; + + /* Custom log level colors */ + --log-fatal-color: #ff0000; + --log-error-color: #ff6b6b; + --log-warning-color: #feca57; + --log-info-color: #48dbfb; + --log-debug-color: #ff9ff3; + + /* Custom filter background */ + --logarea-filter-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} +``` + +#### Corporate Branding +```scss +:root { + /* Corporate colors */ + --primary-color: #1e40af; /* Corporate blue */ + --primary-hover: #1e3a8a; + --success-color: #059669; /* Corporate green */ + --warning-color: #d97706; /* Corporate orange */ + --danger-color: #dc2626; /* Corporate red */ + + /* Corporate typography */ + --input-font-size: 15px; + --button-font-weight: 700; + + /* Corporate spacing */ + --input-padding: 16px; + --button-padding: 16px 32px; + --input-border-radius: 8px; + --button-border-radius: 8px; + + /* Corporate shadows */ + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} +``` + +#### Minimal/Flat Design +```scss +:root { + /* Remove shadows and borders for flat design */ + --shadow-sm: none; + --shadow-md: none; + --shadow-lg: none; + --input-border: 1px solid transparent; + --input-focus-shadow: none; + --dropdown-shadow: none; + + /* Flat colors */ + --input-focus-border: var(--primary-color); + --dropdown-border: 1px solid var(--border-light); + + /* Sharp corners */ + --input-border-radius: 0; + --button-border-radius: 0; + --dropdown-border-radius: 0; +} +``` + +### Component-Level Overrides + +You can also override styles at the component level: + +```vue + + + +``` + +### CSS Class Overrides + +For complete control, you can also override CSS classes: + +```scss +/* Global overrides */ +.log-area { + border: 2px solid var(--primary-color); + + .filter { + background: var(--primary-color); + color: white; + } + + .log-area-item { + &:hover { + background-color: var(--light); + } + } +} + +.dropdown-trigger { + border: 2px solid var(--border); + + &:focus { + border-color: var(--primary-color); + outline: 2px solid var(--primary-color); + outline-offset: 2px; + } +} +``` + +This approach ensures that your components integrate seamlessly with any design system while maintaining consistent behavior and accessibility. + +## Development + +### Build +```bash +npm run build +``` + +### Test +```bash +npm run test +``` + +## License + +MIT \ No newline at end of file diff --git a/packages/vue3-components/package.json b/packages/vue3-components/package.json new file mode 100644 index 0000000..1589a58 --- /dev/null +++ b/packages/vue3-components/package.json @@ -0,0 +1,47 @@ +{ + "name": "@websdr/vue3-components", + "version": "1.0.0", + "description": "This is a Vue 3 components package for WebSDR", + "author": "Timur ", + "license": "MIT", + "keywords": [], + "main": "dist/index.cjs.js", + "module": "dist/index.es.js", + "types": "dist/index.d.ts", + "type": "module", + "scripts": { + "build": "vue-tsc && tsc-alias && tsc-esm-fix && vite build && npm run build:styles && npm run build:types", + "build:styles": "node scripts/build-styles.js", + "build:types": "vue-tsc --emitDeclarationOnly", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" + }, + "dependencies": { + "@websdr/core": "^1.0.0", + "@websdr/frontend-core": "^1.0.0", + "vue": "^3.5.23" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.1", + "@vue/cli-plugin-typescript": "^5.0.9", + "@vue/cli-service": "^5.0.9", + "@vue/test-utils": "^2.4.6", + "happy-dom": "^20.0.10", + "sass": "^1.93.3", + "vue-tsc": "^3.1.3" + }, + "sideEffects": [ + "**/*.css", + "**/*.scss", + "**/*.sass" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.es.js", + "require": "./dist/index.cjs.js" + }, + "./styles/*": "./dist/styles/*" + } +} \ No newline at end of file diff --git a/packages/vue3-components/scripts/build-styles.js b/packages/vue3-components/scripts/build-styles.js new file mode 100644 index 0000000..a0376de --- /dev/null +++ b/packages/vue3-components/scripts/build-styles.js @@ -0,0 +1,54 @@ +import fs from 'fs'; +import path from 'path'; +import { spawnSync } from 'child_process'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const pkgRoot = path.resolve(__dirname, '..'); +const srcDir = path.join(pkgRoot, 'src', 'styles'); +const outDir = path.join(pkgRoot, 'dist', 'styles'); + +function ensureDir(dir) { + fs.mkdirSync(dir, { recursive: true }); +} + +function runSass() { + // Use npx to ensure the use of the local sass + const res = spawnSync('npx', ['sass', `${srcDir}:${outDir}`, '--no-source-map'], { stdio: 'inherit' }); + return res.status === 0; +} + +function copyCssNoOverwrite() { + let files = []; + try { + files = fs.readdirSync(srcDir).filter(f => f.endsWith('.css')); + } catch (e) { + return; + } + + for (const f of files) { + const src = path.join(srcDir, f); + const dest = path.join(outDir, f); + try { + if (!fs.existsSync(dest)) { + fs.copyFileSync(src, dest); + } + } catch (e) { + } + } +} + +(function main() { + console.log('Building styles...'); + try { + ensureDir(outDir); + const ok = runSass(); + copyCssNoOverwrite(); + process.exit(ok ? 0 : 1); + } catch (e) { + console.error(e); + process.exit(1); + } +})(); diff --git a/packages/vue3-components/src/components/Dropdown.vue b/packages/vue3-components/src/components/Dropdown.vue new file mode 100644 index 0000000..16f04c2 --- /dev/null +++ b/packages/vue3-components/src/components/Dropdown.vue @@ -0,0 +1,585 @@ + + + + + + +