commit e47eee39ed9eb143ada0ef2cdaf6255d9b3e37cf Author: stary <834207172@qq.COM> Date: Wed Apr 29 15:18:30 2026 +0800 初始化 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a27bc33 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# References +# https://github.com/MicrosoftDocs/visualstudio-docs/blob/master/docs/ide/editorconfig-code-style-settings-reference.md#example-editorconfig-file + +############################### +# Core EditorConfig Options # +############################### + +root = true + +# All files +[*] +indent_style = space + +# Code files +[*.{ts,js,json,html}] +end_of_line = lf +indent_size = 2 +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..3c5e79b --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,24 @@ +module.exports = { + env: { + node: true, + jest: true, + }, + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:jest/recommended" + ], + parser: "@typescript-eslint/parser", + parserOptions: { + sourceType: "module", + project: "./tsconfig.lint.json", + }, + plugins: ["@typescript-eslint", "jest"], + rules: { + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/semi": ["error"], + "@typescript-eslint/no-extra-semi": ["error"], + } +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d1b35f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,187 @@ +### C++ +# Object files +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Dynamic libraries +*.so +*.dylib +*.dll + +# Static libraries +*.a +*.lib + +# Executables +*.exe +*.out + +# Linker output +*.ilk +*.map +*.exp + +# Debug files +*.dSYM/ +*.idb +*.pdb + +### Node +# Dependencies +node_modules/ + +# Logs +*.log + +# Runtime data +*.pid +*.pid.lock + +# Coverage +coverage/ +*.lcov +.nyc_output + +# Build output +dist/ +build/Release + +# TypeScript cache +*.tsbuildinfo + +# Framework build output and caches +.cache +.parcel-cache +.next +out/ +.nuxt + +# dotenv environment variable files +.env +.env.local +.env.*.local + +# npm cache directory +.npm +*.tgz + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/install-state.gz +.pnp.* + +### Objective-C +# Xcode user settings +xcuserdata/ + +# Xcode build data +DerivedData/ + +# Obj-C/Swift specific +*.hmap + +# App packaging +*.ipa +*.dSYM.zip +*.dSYM + +# Playgrounds +timeline.xctimeline +playground.xcworkspace + +### Python +# Byte-compiled files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +build/ +dist/ +*.egg-info/ +*.egg + +# dotenv environment variable files +.env + +# Virtual environments +.venv +env/ +venv/ + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.pytest_cache/ + +# Type checkers +.mypy_cache/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +# .python-version + +### Rust +# Cargo build output +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# Backup files generated by rustfmt +**/*.rs.bk + +### Windows +# Windows thumbnail cache files +Thumbs.db + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows shortcuts +*.lnk + +### JetBrains +# JetBrains IDE directory +.idea/ + +# CMake build directories +cmake-build-*/ + +# File-based project format +*.iws +*.iml + +# IntelliJ build output +out/ + +### VS Code +# VSCode settings (keep shared configuration) +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix \ No newline at end of file diff --git a/client/.eslintrc.json b/client/.eslintrc.json new file mode 100644 index 0000000..6d31058 --- /dev/null +++ b/client/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "root": true, + "extends": [ + "eslint:recommended", + "plugin:jest/recommended" + ], + "plugins": [ + "jest" + ], + "parserOptions": { + "sourceType": "module", + "ecmaVersion": "latest" + }, + "env": { + "browser": true, + "es6": true , + "jest": true + }, + "rules": { + "semi": "error", + "no-extra-semi": "error" + } +} \ No newline at end of file diff --git a/client/jest.config.js b/client/jest.config.js new file mode 100644 index 0000000..7a52a86 --- /dev/null +++ b/client/jest.config.js @@ -0,0 +1,195 @@ +/* + * For a detailed explanation regarding each configuration property, visit: + * https://jestjs.io/docs/configuration + */ +export default { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "/private/var/folders/wt/swsbjj0x061bdb0y4dqc0g4c0000gn/T/jest_dx", + + // Automatically clear mock calls and instances between every test + // clearMocks: false, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: undefined, + + // The directory where Jest should output its coverage files + coverageDirectory: "coverage", + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "/node_modules/" + // ], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: "v8", + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + moduleFileExtensions: [ + "js", + "jsx", + "ts", + "tsx", + "json", + "node" + ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + // moduleNameMapper: {}, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + // preset: undefined, + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state between every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state between every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + // roots: [ + // "" + // ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + setupFilesAfterEnv: ['./jest.setup.js'], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + testEnvironment: "jest-environment-jsdom", + + // Options that will be passed to the testEnvironment + testEnvironmentOptions: { + url: "http://localhost:8081" + }, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + testMatch: [ + "**/__tests__/**/*.[jt]s?(x)", + "**/?(*.)+(spec|test).[tj]s?(x)" + ], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "/node_modules/" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", + + // This option set timeout each test + testTimeout: 5000, + + // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" + // timers: "real", + + // A map from regular expressions to paths to transformers + // transform: undefined + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "/node_modules/", + // "\\.pnp\\.[^\\/]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; diff --git a/client/jest.setup.js b/client/jest.setup.js new file mode 100644 index 0000000..8101fdb --- /dev/null +++ b/client/jest.setup.js @@ -0,0 +1,35 @@ +/* eslint-disable no-undef */ +import fetch from 'node-fetch'; +import { TextEncoder, TextDecoder } from 'util'; +import { PeerConnectionMock, SessionDescriptionMock, IceCandidateMock } from './test/peerconnectionmock'; +import ResizeObserverMock from './test/resizeobservermock'; + +// note: If set testEnvironment `jest-environment-jsdom`, below classes are not defined. + +if (!window.fetch) { + window.fetch = fetch; +} + +if (!window.TextEncoder) { + window.TextEncoder = TextEncoder; +} + +if (!window.TextDecoder) { + window.TextDecoder = TextDecoder; +} + +if (!window.RTCPeerConnection) { + window.RTCPeerConnection = PeerConnectionMock; +} + +if (!window.RTCSessionDescription) { + window.RTCSessionDescription = SessionDescriptionMock; +} + +if (!window.RTCIceCandidate) { + window.RTCIceCandidate = IceCandidateMock; +} + +if (!window.ResizeObserver) { + window.ResizeObserver = ResizeObserverMock; +} \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json new file mode 100644 index 0000000..d9d9444 --- /dev/null +++ b/client/package-lock.json @@ -0,0 +1,9885 @@ +{ + "name": "webclient", + "version": "3.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "webclient", + "version": "3.1.0", + "devDependencies": { + "eslint": "^8.23.0", + "eslint-plugin-jest": "^27.0.1", + "jest": "^29.0.2", + "jest-dev-server": "^6.1.1", + "jest-environment-jsdom": "^29.0.2", + "node-fetch": "^3.2.10" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.0.tgz", + "integrity": "sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.0.tgz", + "integrity": "sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.19.0", + "@babel/helper-compilation-targets": "^7.19.0", + "@babel/helper-module-transforms": "^7.19.0", + "@babel/helpers": "^7.19.0", + "@babel/parser": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz", + "integrity": "sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.19.0", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", + "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.18.6", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", + "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz", + "integrity": "sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", + "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@eslint/eslintrc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.1.tgz", + "integrity": "sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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 + }, + "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, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", + "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.0.2.tgz", + "integrity": "sha512-Fv02ijyhF4D/Wb3DvZO3iBJQz5DnzpJEIDBDbvje8Em099N889tNMUnBw7SalmSuOI+NflNG40RA1iK71kImPw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.0.2", + "jest-util": "^29.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.0.2.tgz", + "integrity": "sha512-imP5M6cdpHEOkmcuFYZuM5cTG1DAF7ZlVNCq1+F7kbqme2Jcl+Kh4M78hihM76DJHNkurbv4UVOnejGxBKEmww==", + "dev": true, + "dependencies": { + "@jest/console": "^29.0.2", + "@jest/reporters": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.0.0", + "jest-config": "^29.0.2", + "jest-haste-map": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-resolve": "^29.0.2", + "jest-resolve-dependencies": "^29.0.2", + "jest-runner": "^29.0.2", + "jest-runtime": "^29.0.2", + "jest-snapshot": "^29.0.2", + "jest-util": "^29.0.2", + "jest-validate": "^29.0.2", + "jest-watcher": "^29.0.2", + "micromatch": "^4.0.4", + "pretty-format": "^29.0.2", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.0.2.tgz", + "integrity": "sha512-Yf+EYaLOrVCgts/aTS5nGznU4prZUPa5k9S63Yct8YSOKj2jkdS17hHSUKhk5jxDFMyCy1PXknypDw7vfgc/mA==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "jest-mock": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.0.2.tgz", + "integrity": "sha512-y/3geZ92p2/zovBm/F+ZjXUJ3thvT9IRzD6igqaWskFE2aR0idD+N/p5Lj/ZautEox/9RwEc6nqergebeh72uQ==", + "dev": true, + "dependencies": { + "expect": "^29.0.2", + "jest-snapshot": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.0.2.tgz", + "integrity": "sha512-+wcQF9khXKvAEi8VwROnCWWmHfsJYCZAs5dmuMlJBKk57S6ZN2/FQMIlo01F29fJyT8kV/xblE7g3vkIdTLOjw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.0.2.tgz", + "integrity": "sha512-2JhQeWU28fvmM5r33lxg6BxxkTKaVXs6KMaJ6eXSM8ml/MaWkt2BvbIO8G9KWAJFMdBXWbn+2h9OK1/s5urKZA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^29.0.2", + "jest-mock": "^29.0.2", + "jest-util": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.0.2.tgz", + "integrity": "sha512-4hcooSNJCVXuTu07/VJwCWW6HTnjLtQdqlcGisK6JST7z2ixa8emw4SkYsOk7j36WRc2ZUEydlUePnOIOTCNXg==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.0.2", + "@jest/expect": "^29.0.2", + "@jest/types": "^29.0.2", + "jest-mock": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.0.2.tgz", + "integrity": "sha512-Kr41qejRQHHkCgWHC9YwSe7D5xivqP4XML+PvgwsnRFaykKdNflDUb4+xLXySOU+O/bPkVdFpGzUpVNSJChCrw==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.0.2", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.0.0.tgz", + "integrity": "sha512-nOr+0EM8GiHf34mq2GcJyz/gYFyLQ2INDhAylrZJ9mMWoW21mLBfZa0BUVPPMxVYrLjeiRe2Z7kWXOGnS0TFhQ==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.0.2.tgz", + "integrity": "sha512-b5rDc0lLL6Kx73LyCx6370k9uZ8o5UKdCpMS6Za3ke7H9y8PtAU305y6TeghpBmf2In8p/qqi3GpftgzijSsNw==", + "dev": true, + "dependencies": { + "@jest/console": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.0.2.tgz", + "integrity": "sha512-fsyZqHBlXNMv5ZqjQwCuYa2pskXCO0DVxh5aaVCuAtwzHuYEGrhordyEncBLQNuCGQSYgElrEEmS+7wwFnnMKw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.0.2", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.0.2.tgz", + "integrity": "sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.2.tgz", + "integrity": "sha512-5WNMesBLmlkt1+fVkoCjHa0X3i3q8zc4QLTDkdHgCa2gyPZc7rdlZBWgVLqwS1860ZW5xJuCDwAzqbGaXIr/ew==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.0.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dev": true, + "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 + }, + "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 + }, + "node_modules/@sinclair/typebox": { + "version": "0.24.35", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.35.tgz", + "integrity": "sha512-iN6ehuDndiTiDz2F+Orv/+oHJR+PrGv+38oghCddpsW4YEZl5qyLsWxSwYUWrKEOfjpGtXDFW6scJtjpzSLeSw==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.1.19", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", + "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz", + "integrity": "sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jsdom": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz", + "integrity": "sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.7.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.15.tgz", + "integrity": "sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ==", + "dev": true + }, + "node_modules/@types/prettier": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", + "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz", + "integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.27.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.27.1.tgz", + "integrity": "sha512-fQEOSa/QroWE6fAEg+bJxtRZJTH8NTskggybogHt4H9Da8zd4cJji76gA5SBlR0MgtwF7rebxTbDKB49YUCpAg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.27.1", + "@typescript-eslint/visitor-keys": "5.27.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.27.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.27.1.tgz", + "integrity": "sha512-LgogNVkBhCTZU/m8XgEYIWICD6m4dmEDbKXESCbqOXfKZxRKeqpiJXQIErv66sdopRKZPo5l32ymNqibYEH/xg==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.27.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.1.tgz", + "integrity": "sha512-DnZvvq3TAJ5ke+hk0LklvxwYsnXpRdqUY5gaVS0D4raKtbznPz71UJGnPTHEFo0GDxqLOLdMkkmVZjSpET1hFw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.27.1", + "@typescript-eslint/visitor-keys": "5.27.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.27.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.27.1.tgz", + "integrity": "sha512-mZ9WEn1ZLDaVrhRaYgzbkXBkTPghPFsup8zDbbsYTxC5OmqrFE7skkKS/sraVsLP3TcT3Ki5CSyEFBRkLH/H/w==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.27.1", + "@typescript-eslint/types": "5.27.1", + "@typescript-eslint/typescript-estree": "5.27.1", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.27.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.1.tgz", + "integrity": "sha512-xYs6ffo01nhdJgPieyk7HAOpjhTsx7r/oB9LWEhwAXgwn33tkr+W8DI2ChboqhZlC4q3TC6geDYPoiX8ROqyOQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.27.1", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/axios": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", + "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.14.7" + } + }, + "node_modules/babel-jest": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.0.2.tgz", + "integrity": "sha512-yTu4/WSi/HzarjQtrJSwV+/0maoNt+iP0DmpvFJdv9yY+5BuNle8TbheHzzcSWj5gIHfuhpbLYHWRDYhWKyeKQ==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.0.2", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.0.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.0.2.tgz", + "integrity": "sha512-eBr2ynAEFjcebVvu8Ktx580BD1QKCrBG1XwEUTXJe285p9HA/4hOhfWCFRQhTKSyBV0VzjhG7H91Eifz9s29hg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.0.2.tgz", + "integrity": "sha512-BeVXp7rH5TK96ofyEnHjznjLMQ2nAeDJ+QzxKnHAAMs0RgrQsCywjAN8m4mOm5Di0pxU//3AoEeJJrerMH5UeA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.0.2", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", + "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001370", + "electron-to-chromium": "^1.4.202", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.5" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001390", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001390.tgz", + "integrity": "sha512-sS4CaUM+/+vqQUlCvCJ2WtDlV81aWtHhqeEVkLokVJJa3ViN4zDxAGfq9R8i1m90uGHxo99cy10Od+lvn3hf0g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", + "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", + "dev": true + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "node_modules/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, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "node_modules/cwd": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz", + "integrity": "sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==", + "dev": true, + "dependencies": { + "find-pkg": "^0.1.2", + "fs-exists-sync": "^0.1.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", + "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", + "dev": true + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.0.0.tgz", + "integrity": "sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.242", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.242.tgz", + "integrity": "sha512-nPdgMWtjjWGCtreW/2adkrB2jyHjClo9PtVhR6rW+oxa4E4Wom642Tn+5LslHP3XPL5MCpkn5/UEY60EXylNeQ==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.23.0.tgz", + "integrity": "sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.3.1", + "@humanwhocodes/config-array": "^0.10.4", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@humanwhocodes/module-importer": "^1.0.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.0.1.tgz", + "integrity": "sha512-LosUsrkwVSs/8Z/I8Hqn5vWgTEsHrfIquDEKOsV8/cl+gbFR4tiRCE1AimEotsHjSC0Rx1tYm6vPhw8C3ktmmg==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^5.10.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", + "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-tilde": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", + "integrity": "sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==", + "dev": true, + "dependencies": { + "os-homedir": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.0.2.tgz", + "integrity": "sha512-JeJlAiLKn4aApT4pzUXBVxl3NaZidWIOdg//smaIlP9ZMBDkHZGFd9ubphUZP9pUyDEo7bC6M0IIZR51o75qQw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.0.2", + "jest-get-type": "^29.0.0", + "jest-matcher-utils": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-util": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fetch-blob": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz", + "integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-file-up": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz", + "integrity": "sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==", + "dev": true, + "dependencies": { + "fs-exists-sync": "^0.1.0", + "resolve-dir": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-pkg": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-0.1.2.tgz", + "integrity": "sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==", + "dev": true, + "dependencies": { + "find-file-up": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-process": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.7.tgz", + "integrity": "sha512-/U4CYp1214Xrp3u3Fqr9yNynUrr5Le4y0SsJh2lMDDSbpwYSz3M2SMWQC+wqcx79cN8PQtHQIL8KnuY9M66fdg==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "commander": "^5.1.0", + "debug": "^4.1.1" + }, + "bin": { + "find-process": "bin/find-process.js" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global-modules": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", + "integrity": "sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==", + "dev": true, + "dependencies": { + "global-prefix": "^0.1.4", + "is-windows": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", + "integrity": "sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.0", + "ini": "^1.3.4", + "is-windows": "^0.2.0", + "which": "^1.2.12" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", + "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.0.2.tgz", + "integrity": "sha512-enziNbNUmXTcTaTP/Uq5rV91r0Yqy2UKzLUIabxMpGm9YHz8qpbJhiRnNVNvm6vzWfzt/0o97NEHH8/3udoClA==", + "dev": true, + "dependencies": { + "@jest/core": "^29.0.2", + "@jest/types": "^29.0.2", + "import-local": "^3.0.2", + "jest-cli": "^29.0.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.0.0.tgz", + "integrity": "sha512-28/iDMDrUpGoCitTURuDqUzWQoWmOmOKOFST1mi2lwh62X4BFf6khgH3uSuo1e49X/UDjuApAj3w0wLOex4VPQ==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.0.2.tgz", + "integrity": "sha512-YTPEsoE1P1X0bcyDQi3QIkpt2Wl9om9k2DQRuLFdS5x8VvAKSdYAVJufgvudhnKgM8WHvvAzhBE+1DRQB8x1CQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.0.2", + "@jest/expect": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.0.2", + "jest-matcher-utils": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-runtime": "^29.0.2", + "jest-snapshot": "^29.0.2", + "jest-util": "^29.0.2", + "p-limit": "^3.1.0", + "pretty-format": "^29.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.0.2.tgz", + "integrity": "sha512-tlf8b+4KcUbBGr25cywIi3+rbZ4+G+SiG8SvY552m9sRZbXPafdmQRyeVE/C/R8K+TiBAMrTIUmV2SlStRJ40g==", + "dev": true, + "dependencies": { + "@jest/core": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/types": "^29.0.2", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.0.2", + "jest-util": "^29.0.2", + "jest-validate": "^29.0.2", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.0.2.tgz", + "integrity": "sha512-RU4gzeUNZAFktYVzDGimDxeYoaiTnH100jkYYZgldqFamaZukF0IqmFx8+QrzVeEWccYg10EEJT3ox1Dq5b74w==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.0.2", + "@jest/types": "^29.0.2", + "babel-jest": "^29.0.2", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.0.2", + "jest-environment-node": "^29.0.2", + "jest-get-type": "^29.0.0", + "jest-regex-util": "^29.0.0", + "jest-resolve": "^29.0.2", + "jest-runner": "^29.0.2", + "jest-util": "^29.0.2", + "jest-validate": "^29.0.2", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.0.2", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-dev-server": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-6.1.1.tgz", + "integrity": "sha512-z5LnaGDvlIkdMv/rppSO4+rq+GyQKf1xI9oiBxf9/2EBeN2hxRaWiMvaLNDnHPZj2PAhBXsycrKslDDoZO2Xtw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "cwd": "^0.10.0", + "find-process": "^1.4.7", + "prompts": "^2.4.2", + "spawnd": "^6.0.2", + "tree-kill": "^1.2.2", + "wait-on": "^6.0.1" + } + }, + "node_modules/jest-diff": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.0.2.tgz", + "integrity": "sha512-b9l9970sa1rMXH1owp2Woprmy42qIwwll/htsw4Gf7+WuSp5bZxNhkKHDuCGKL+HoHn1KhcC+tNEeAPYBkD2Jg==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.0.0", + "jest-get-type": "^29.0.0", + "pretty-format": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.0.0.tgz", + "integrity": "sha512-s5Kpra/kLzbqu9dEjov30kj1n4tfu3e7Pl8v+f8jOkeWNqM6Ds8jRaJfZow3ducoQUrf2Z4rs2N5S3zXnb83gw==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.0.2.tgz", + "integrity": "sha512-+sA9YjrJl35iCg0W0VCrgCVj+wGhDrrKQ+YAqJ/DHBC4gcDFAeePtRRhpJnX9gvOZ63G7gt52pwp2PesuSEx0Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "chalk": "^4.0.0", + "jest-get-type": "^29.0.0", + "jest-util": "^29.0.2", + "pretty-format": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.0.2.tgz", + "integrity": "sha512-hWqC9FQI5yT04lTd4VJnzT5QObxq0xrSrqpGkqsYfxPeJYjyhriI7W2oJC5HZ1UbhnvA+8GS1nzgPsstvRpdVw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.0.2", + "@jest/fake-timers": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.0.2", + "jest-util": "^29.0.2", + "jsdom": "^20.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.0.2.tgz", + "integrity": "sha512-4Fv8GXVCToRlMzDO94gvA8iOzKxQ7rhAbs8L+j8GPyTxGuUiYkV+63LecGeVdVhsL2KXih1sKnoqmH6tp89J7Q==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.0.2", + "@jest/fake-timers": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "jest-mock": "^29.0.2", + "jest-util": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.0.0.tgz", + "integrity": "sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.0.2.tgz", + "integrity": "sha512-5f0493qDeAxjUldkBSQg5D1cLadRgZVyWpTQvfJeQwQUpHQInE21AyVHVv64M7P2Ue8Z5EZ4BAcoDS/dSPPgMw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.0.0", + "pretty-format": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.0.2.tgz", + "integrity": "sha512-s62YkHFBfAx0JLA2QX1BlnCRFwHRobwAv2KP1+YhjzF6ZCbCVrf1sG8UJyn62ZUsDaQKpoo86XMTjkUyO5aWmQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.0.2", + "jest-get-type": "^29.0.0", + "pretty-format": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.2.tgz", + "integrity": "sha512-kcJAgms3ckJV0wUoLsAM40xAhY+pb9FVSZwicjFU9PFkaTNmqh9xd99/CzKse48wPM1ANUQKmp03/DpkY+lGrA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.0.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.0.2.tgz", + "integrity": "sha512-giWXOIT23UCxHCN2VUfUJ0Q7SmiqQwfSFXlCaIhW5anITpNQ+3vuLPQdKt5wkuwM37GrbFyHIClce8AAK9ft9g==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "@types/node": "*" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.0.2.tgz", + "integrity": "sha512-V3uLjSA+EHxLtjIDKTBXnY71hyx+8lusCqPXvqzkFO1uCGvVpjBfuOyp+KOLBNSuY61kM2jhepiMwt4eiJS+Vw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.0.2", + "jest-validate": "^29.0.2", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.0.2.tgz", + "integrity": "sha512-fSAu6eIG7wtGdnPJUkVVdILGzYAP9Dj/4+zvC8BrGe8msaUMJ9JeygU0Hf9+Uor6/icbuuzQn5See1uajLnAqg==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.0.0", + "jest-snapshot": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.0.2.tgz", + "integrity": "sha512-+D82iPZejI8t+SfduOO1deahC/QgLFf8aJBO++Znz3l2ETtOMdM7K4ATsGWzCFnTGio5yHaRifg1Su5Ybza5Nw==", + "dev": true, + "dependencies": { + "@jest/console": "^29.0.2", + "@jest/environment": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.0.0", + "jest-environment-node": "^29.0.2", + "jest-haste-map": "^29.0.2", + "jest-leak-detector": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-resolve": "^29.0.2", + "jest-runtime": "^29.0.2", + "jest-util": "^29.0.2", + "jest-watcher": "^29.0.2", + "jest-worker": "^29.0.2", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.0.2.tgz", + "integrity": "sha512-DO6F81LX4okOgjJLkLySv10E5YcV5NHUbY1ZqAUtofxdQE+q4hjH0P2gNsY8x3z3sqgw7O/+919SU4r18Fcuig==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.0.2", + "@jest/fake-timers": "^29.0.2", + "@jest/globals": "^29.0.2", + "@jest/source-map": "^29.0.0", + "@jest/test-result": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-mock": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-resolve": "^29.0.2", + "jest-snapshot": "^29.0.2", + "jest-util": "^29.0.2", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.0.2.tgz", + "integrity": "sha512-26C4PzGKaX5gkoKg8UzYGVy2HPVcTaROSkf0gwnHu3lGeTB7bAIJBovvVPZoiJ20IximJELQs/r8WSDRCuGX2A==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.0.2", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.0.2", + "jest-get-type": "^29.0.0", + "jest-haste-map": "^29.0.2", + "jest-matcher-utils": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-util": "^29.0.2", + "natural-compare": "^1.4.0", + "pretty-format": "^29.0.2", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.2.tgz", + "integrity": "sha512-ozk8ruEEEACxqpz0hN9UOgtPZS0aN+NffwQduR5dVlhN+eN47vxurtvgZkYZYMpYrsmlAEx1XabkB3BnN0GfKQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.0.2.tgz", + "integrity": "sha512-AeRKm7cEucSy7tr54r3LhiGIXYvOILUwBM1S7jQkKs6YelwAlWKsmZGVrQR7uwsd31rBTnR5NQkODi1Z+6TKIQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.0.0", + "leven": "^3.1.0", + "pretty-format": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.0.2.tgz", + "integrity": "sha512-ds2bV0oyUdYoyrUTv4Ga5uptz4cEvmmP/JzqDyzZZanvrIn8ipxg5l3SDOAIiyuAx1VdHd2FBzeXPFO5KPH8vQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^29.0.2", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/joi": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz", + "integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", + "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.7.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "^7.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^3.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.8.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", + "dev": true + }, + "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, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse5": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.1.tgz", + "integrity": "sha512-kwpuwzB+px5WUg9pyK0IcK/shltJN5/OVhQagxhCQNtT9Y9QRZqNY2e1cmbu/paRh5LMnz/oVTVLBpjFmMZhSg==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.2.tgz", + "integrity": "sha512-wp3CdtUa3cSJVFn3Miu5a1+pxc1iPIQTenOAn+x5erXeN1+ryTcLesV5pbK/rlW5EKwp27x38MoYfNGaNXDDhg==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "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" + } + ] + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/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, + "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 + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", + "integrity": "sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==", + "dev": true, + "dependencies": { + "expand-tilde": "^1.2.2", + "global-modules": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", + "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", + "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/rxjs/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/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, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spawnd": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-6.0.2.tgz", + "integrity": "sha512-+YJtx0dvy2wt304MrHD//tASc84zinBUYU1jacPBzrjhZUd7RsDo25krxr4HUHAQzEQFuMAs4/p+yLYU5ciZ1w==", + "dev": true, + "dependencies": { + "exit": "^0.1.2", + "signal-exit": "^3.0.6", + "tree-kill": "^1.2.2" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "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, + "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, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz", + "integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz", + "integrity": "sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "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, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", + "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", + "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", + "dev": true, + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/wait-on": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-6.0.1.tgz", + "integrity": "sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==", + "dev": true, + "dependencies": { + "axios": "^0.25.0", + "joi": "^17.6.0", + "lodash": "^4.17.21", + "minimist": "^1.2.5", + "rxjs": "^7.5.4" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "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 + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "dev": true, + "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/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dev": true, + "requires": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "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, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "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 + }, + "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 + }, + "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, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/compat-data": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.0.tgz", + "integrity": "sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw==", + "dev": true + }, + "@babel/core": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.0.tgz", + "integrity": "sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.19.0", + "@babel/helper-compilation-targets": "^7.19.0", + "@babel/helper-module-transforms": "^7.19.0", + "@babel/helpers": "^7.19.0", + "@babel/parser": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "dev": true, + "requires": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz", + "integrity": "sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.19.0", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", + "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.18.6", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", + "dev": true + }, + "@babel/helper-simple-access": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", + "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz", + "integrity": "sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==", + "dev": true, + "requires": { + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0" + } + }, + "@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "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, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "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 + }, + "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 + }, + "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, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "dev": true + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", + "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + } + }, + "@babel/traverse": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.1.tgz", + "integrity": "sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@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 + }, + "@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@humanwhocodes/config-array": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", + "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "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, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "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, + "requires": { + "p-locate": "^4.1.0" + } + }, + "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, + "requires": { + "p-try": "^2.0.0" + } + }, + "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, + "requires": { + "p-limit": "^2.2.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.0.2.tgz", + "integrity": "sha512-Fv02ijyhF4D/Wb3DvZO3iBJQz5DnzpJEIDBDbvje8Em099N889tNMUnBw7SalmSuOI+NflNG40RA1iK71kImPw==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.0.2", + "jest-util": "^29.0.2", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.0.2.tgz", + "integrity": "sha512-imP5M6cdpHEOkmcuFYZuM5cTG1DAF7ZlVNCq1+F7kbqme2Jcl+Kh4M78hihM76DJHNkurbv4UVOnejGxBKEmww==", + "dev": true, + "requires": { + "@jest/console": "^29.0.2", + "@jest/reporters": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.0.0", + "jest-config": "^29.0.2", + "jest-haste-map": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-resolve": "^29.0.2", + "jest-resolve-dependencies": "^29.0.2", + "jest-runner": "^29.0.2", + "jest-runtime": "^29.0.2", + "jest-snapshot": "^29.0.2", + "jest-util": "^29.0.2", + "jest-validate": "^29.0.2", + "jest-watcher": "^29.0.2", + "micromatch": "^4.0.4", + "pretty-format": "^29.0.2", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "@jest/environment": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.0.2.tgz", + "integrity": "sha512-Yf+EYaLOrVCgts/aTS5nGznU4prZUPa5k9S63Yct8YSOKj2jkdS17hHSUKhk5jxDFMyCy1PXknypDw7vfgc/mA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "jest-mock": "^29.0.2" + } + }, + "@jest/expect": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.0.2.tgz", + "integrity": "sha512-y/3geZ92p2/zovBm/F+ZjXUJ3thvT9IRzD6igqaWskFE2aR0idD+N/p5Lj/ZautEox/9RwEc6nqergebeh72uQ==", + "dev": true, + "requires": { + "expect": "^29.0.2", + "jest-snapshot": "^29.0.2" + } + }, + "@jest/expect-utils": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.0.2.tgz", + "integrity": "sha512-+wcQF9khXKvAEi8VwROnCWWmHfsJYCZAs5dmuMlJBKk57S6ZN2/FQMIlo01F29fJyT8kV/xblE7g3vkIdTLOjw==", + "dev": true, + "requires": { + "jest-get-type": "^29.0.0" + } + }, + "@jest/fake-timers": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.0.2.tgz", + "integrity": "sha512-2JhQeWU28fvmM5r33lxg6BxxkTKaVXs6KMaJ6eXSM8ml/MaWkt2BvbIO8G9KWAJFMdBXWbn+2h9OK1/s5urKZA==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^29.0.2", + "jest-mock": "^29.0.2", + "jest-util": "^29.0.2" + } + }, + "@jest/globals": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.0.2.tgz", + "integrity": "sha512-4hcooSNJCVXuTu07/VJwCWW6HTnjLtQdqlcGisK6JST7z2ixa8emw4SkYsOk7j36WRc2ZUEydlUePnOIOTCNXg==", + "dev": true, + "requires": { + "@jest/environment": "^29.0.2", + "@jest/expect": "^29.0.2", + "@jest/types": "^29.0.2", + "jest-mock": "^29.0.2" + } + }, + "@jest/reporters": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.0.2.tgz", + "integrity": "sha512-Kr41qejRQHHkCgWHC9YwSe7D5xivqP4XML+PvgwsnRFaykKdNflDUb4+xLXySOU+O/bPkVdFpGzUpVNSJChCrw==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.0.2", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^9.0.1" + } + }, + "@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@jest/source-map": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.0.0.tgz", + "integrity": "sha512-nOr+0EM8GiHf34mq2GcJyz/gYFyLQ2INDhAylrZJ9mMWoW21mLBfZa0BUVPPMxVYrLjeiRe2Z7kWXOGnS0TFhQ==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.0.2.tgz", + "integrity": "sha512-b5rDc0lLL6Kx73LyCx6370k9uZ8o5UKdCpMS6Za3ke7H9y8PtAU305y6TeghpBmf2In8p/qqi3GpftgzijSsNw==", + "dev": true, + "requires": { + "@jest/console": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.0.2.tgz", + "integrity": "sha512-fsyZqHBlXNMv5ZqjQwCuYa2pskXCO0DVxh5aaVCuAtwzHuYEGrhordyEncBLQNuCGQSYgElrEEmS+7wwFnnMKw==", + "dev": true, + "requires": { + "@jest/test-result": "^29.0.2", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.0.2.tgz", + "integrity": "sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + } + }, + "@jest/types": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.2.tgz", + "integrity": "sha512-5WNMesBLmlkt1+fVkoCjHa0X3i3q8zc4QLTDkdHgCa2gyPZc7rdlZBWgVLqwS1860ZW5xJuCDwAzqbGaXIr/ew==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "@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, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@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 + }, + "@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, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@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 + }, + "@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 + }, + "@sinclair/typebox": { + "version": "0.24.35", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.35.tgz", + "integrity": "sha512-iN6ehuDndiTiDz2F+Orv/+oHJR+PrGv+38oghCddpsW4YEZl5qyLsWxSwYUWrKEOfjpGtXDFW6scJtjpzSLeSw==", + "dev": true + }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "@types/babel__core": { + "version": "7.1.19", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", + "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz", + "integrity": "sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jsdom": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz", + "integrity": "sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/node": { + "version": "18.7.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.15.tgz", + "integrity": "sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ==", + "dev": true + }, + "@types/prettier": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", + "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/tough-cookie": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "dev": true + }, + "@types/yargs": { + "version": "17.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz", + "integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "@typescript-eslint/scope-manager": { + "version": "5.27.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.27.1.tgz", + "integrity": "sha512-fQEOSa/QroWE6fAEg+bJxtRZJTH8NTskggybogHt4H9Da8zd4cJji76gA5SBlR0MgtwF7rebxTbDKB49YUCpAg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.27.1", + "@typescript-eslint/visitor-keys": "5.27.1" + } + }, + "@typescript-eslint/types": { + "version": "5.27.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.27.1.tgz", + "integrity": "sha512-LgogNVkBhCTZU/m8XgEYIWICD6m4dmEDbKXESCbqOXfKZxRKeqpiJXQIErv66sdopRKZPo5l32ymNqibYEH/xg==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.27.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.1.tgz", + "integrity": "sha512-DnZvvq3TAJ5ke+hk0LklvxwYsnXpRdqUY5gaVS0D4raKtbznPz71UJGnPTHEFo0GDxqLOLdMkkmVZjSpET1hFw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.27.1", + "@typescript-eslint/visitor-keys": "5.27.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.27.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.27.1.tgz", + "integrity": "sha512-mZ9WEn1ZLDaVrhRaYgzbkXBkTPghPFsup8zDbbsYTxC5OmqrFE7skkKS/sraVsLP3TcT3Ki5CSyEFBRkLH/H/w==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.27.1", + "@typescript-eslint/types": "5.27.1", + "@typescript-eslint/typescript-estree": "5.27.1", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "dependencies": { + "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, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.27.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.1.tgz", + "integrity": "sha512-xYs6ffo01nhdJgPieyk7HAOpjhTsx7r/oB9LWEhwAXgwn33tkr+W8DI2ChboqhZlC4q3TC6geDYPoiX8ROqyOQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.27.1", + "eslint-visitor-keys": "^3.3.0" + } + }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true + }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } + } + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "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 + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "axios": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", + "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "dev": true, + "requires": { + "follow-redirects": "^1.14.7" + } + }, + "babel-jest": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.0.2.tgz", + "integrity": "sha512-yTu4/WSi/HzarjQtrJSwV+/0maoNt+iP0DmpvFJdv9yY+5BuNle8TbheHzzcSWj5gIHfuhpbLYHWRDYhWKyeKQ==", + "dev": true, + "requires": { + "@jest/transform": "^29.0.2", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.0.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.0.2.tgz", + "integrity": "sha512-eBr2ynAEFjcebVvu8Ktx580BD1QKCrBG1XwEUTXJe285p9HA/4hOhfWCFRQhTKSyBV0VzjhG7H91Eifz9s29hg==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.0.2.tgz", + "integrity": "sha512-BeVXp7rH5TK96ofyEnHjznjLMQ2nAeDJ+QzxKnHAAMs0RgrQsCywjAN8m4mOm5Di0pxU//3AoEeJJrerMH5UeA==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^29.0.2", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "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 + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "browserslist": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", + "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001370", + "electron-to-chromium": "^1.4.202", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.5" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "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 + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001390", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001390.tgz", + "integrity": "sha512-sS4CaUM+/+vqQUlCvCJ2WtDlV81aWtHhqeEVkLokVJJa3ViN4zDxAGfq9R8i1m90uGHxo99cy10Od+lvn3hf0g==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "ci-info": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", + "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "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, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "cwd": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz", + "integrity": "sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==", + "dev": true, + "requires": { + "find-pkg": "^0.1.2", + "fs-exists-sync": "^0.1.0" + } + }, + "data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", + "dev": true + }, + "data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decimal.js": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", + "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", + "dev": true + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff-sequences": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.0.0.tgz", + "integrity": "sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA==", + "dev": true + }, + "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, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "requires": { + "webidl-conversions": "^7.0.0" + } + }, + "electron-to-chromium": { + "version": "1.4.242", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.242.tgz", + "integrity": "sha512-nPdgMWtjjWGCtreW/2adkrB2jyHjClo9PtVhR6rW+oxa4E4Wom642Tn+5LslHP3XPL5MCpkn5/UEY60EXylNeQ==", + "dev": true + }, + "emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + } + } + }, + "eslint": { + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.23.0.tgz", + "integrity": "sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.3.1", + "@humanwhocodes/config-array": "^0.10.4", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@humanwhocodes/module-importer": "^1.0.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + } + }, + "eslint-plugin-jest": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.0.1.tgz", + "integrity": "sha512-LosUsrkwVSs/8Z/I8Hqn5vWgTEsHrfIquDEKOsV8/cl+gbFR4tiRCE1AimEotsHjSC0Rx1tYm6vPhw8C3ktmmg==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "^5.10.0" + } + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", + "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "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, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expand-tilde": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", + "integrity": "sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==", + "dev": true, + "requires": { + "os-homedir": "^1.0.1" + } + }, + "expect": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.0.2.tgz", + "integrity": "sha512-JeJlAiLKn4aApT4pzUXBVxl3NaZidWIOdg//smaIlP9ZMBDkHZGFd9ubphUZP9pUyDEo7bC6M0IIZR51o75qQw==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.0.2", + "jest-get-type": "^29.0.0", + "jest-matcher-utils": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-util": "^29.0.2" + } + }, + "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 + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "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 + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "fetch-blob": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz", + "integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==", + "dev": true, + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-file-up": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz", + "integrity": "sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==", + "dev": true, + "requires": { + "fs-exists-sync": "^0.1.0", + "resolve-dir": "^0.1.0" + } + }, + "find-pkg": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-0.1.2.tgz", + "integrity": "sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==", + "dev": true, + "requires": { + "find-file-up": "^0.1.2" + } + }, + "find-process": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.7.tgz", + "integrity": "sha512-/U4CYp1214Xrp3u3Fqr9yNynUrr5Le4y0SsJh2lMDDSbpwYSz3M2SMWQC+wqcx79cN8PQtHQIL8KnuY9M66fdg==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "commander": "^5.1.0", + "debug": "^4.1.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "dev": true + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "requires": { + "fetch-blob": "^3.1.2" + } + }, + "fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==", + "dev": true + }, + "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 + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "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 + }, + "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 + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "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 + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "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, + "requires": { + "is-glob": "^4.0.3" + } + }, + "global-modules": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", + "integrity": "sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==", + "dev": true, + "requires": { + "global-prefix": "^0.1.4", + "is-windows": "^0.2.0" + } + }, + "global-prefix": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", + "integrity": "sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.0", + "ini": "^1.3.4", + "is-windows": "^0.2.0", + "which": "^1.2.12" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "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 + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "requires": { + "whatwg-encoding": "^2.0.0" + } + }, + "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 + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "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 + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "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 + }, + "is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "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, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "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 + }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "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 + }, + "is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", + "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.0.2.tgz", + "integrity": "sha512-enziNbNUmXTcTaTP/Uq5rV91r0Yqy2UKzLUIabxMpGm9YHz8qpbJhiRnNVNvm6vzWfzt/0o97NEHH8/3udoClA==", + "dev": true, + "requires": { + "@jest/core": "^29.0.2", + "@jest/types": "^29.0.2", + "import-local": "^3.0.2", + "jest-cli": "^29.0.2" + } + }, + "jest-changed-files": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.0.0.tgz", + "integrity": "sha512-28/iDMDrUpGoCitTURuDqUzWQoWmOmOKOFST1mi2lwh62X4BFf6khgH3uSuo1e49X/UDjuApAj3w0wLOex4VPQ==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + } + }, + "jest-circus": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.0.2.tgz", + "integrity": "sha512-YTPEsoE1P1X0bcyDQi3QIkpt2Wl9om9k2DQRuLFdS5x8VvAKSdYAVJufgvudhnKgM8WHvvAzhBE+1DRQB8x1CQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.0.2", + "@jest/expect": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.0.2", + "jest-matcher-utils": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-runtime": "^29.0.2", + "jest-snapshot": "^29.0.2", + "jest-util": "^29.0.2", + "p-limit": "^3.1.0", + "pretty-format": "^29.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-cli": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.0.2.tgz", + "integrity": "sha512-tlf8b+4KcUbBGr25cywIi3+rbZ4+G+SiG8SvY552m9sRZbXPafdmQRyeVE/C/R8K+TiBAMrTIUmV2SlStRJ40g==", + "dev": true, + "requires": { + "@jest/core": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/types": "^29.0.2", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.0.2", + "jest-util": "^29.0.2", + "jest-validate": "^29.0.2", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + } + }, + "jest-config": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.0.2.tgz", + "integrity": "sha512-RU4gzeUNZAFktYVzDGimDxeYoaiTnH100jkYYZgldqFamaZukF0IqmFx8+QrzVeEWccYg10EEJT3ox1Dq5b74w==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.0.2", + "@jest/types": "^29.0.2", + "babel-jest": "^29.0.2", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.0.2", + "jest-environment-node": "^29.0.2", + "jest-get-type": "^29.0.0", + "jest-regex-util": "^29.0.0", + "jest-resolve": "^29.0.2", + "jest-runner": "^29.0.2", + "jest-util": "^29.0.2", + "jest-validate": "^29.0.2", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.0.2", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + } + }, + "jest-dev-server": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-6.1.1.tgz", + "integrity": "sha512-z5LnaGDvlIkdMv/rppSO4+rq+GyQKf1xI9oiBxf9/2EBeN2hxRaWiMvaLNDnHPZj2PAhBXsycrKslDDoZO2Xtw==", + "dev": true, + "requires": { + "chalk": "^4.1.2", + "cwd": "^0.10.0", + "find-process": "^1.4.7", + "prompts": "^2.4.2", + "spawnd": "^6.0.2", + "tree-kill": "^1.2.2", + "wait-on": "^6.0.1" + } + }, + "jest-diff": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.0.2.tgz", + "integrity": "sha512-b9l9970sa1rMXH1owp2Woprmy42qIwwll/htsw4Gf7+WuSp5bZxNhkKHDuCGKL+HoHn1KhcC+tNEeAPYBkD2Jg==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.0.0", + "jest-get-type": "^29.0.0", + "pretty-format": "^29.0.2" + } + }, + "jest-docblock": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.0.0.tgz", + "integrity": "sha512-s5Kpra/kLzbqu9dEjov30kj1n4tfu3e7Pl8v+f8jOkeWNqM6Ds8jRaJfZow3ducoQUrf2Z4rs2N5S3zXnb83gw==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.0.2.tgz", + "integrity": "sha512-+sA9YjrJl35iCg0W0VCrgCVj+wGhDrrKQ+YAqJ/DHBC4gcDFAeePtRRhpJnX9gvOZ63G7gt52pwp2PesuSEx0Q==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "chalk": "^4.0.0", + "jest-get-type": "^29.0.0", + "jest-util": "^29.0.2", + "pretty-format": "^29.0.2" + } + }, + "jest-environment-jsdom": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.0.2.tgz", + "integrity": "sha512-hWqC9FQI5yT04lTd4VJnzT5QObxq0xrSrqpGkqsYfxPeJYjyhriI7W2oJC5HZ1UbhnvA+8GS1nzgPsstvRpdVw==", + "dev": true, + "requires": { + "@jest/environment": "^29.0.2", + "@jest/fake-timers": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.0.2", + "jest-util": "^29.0.2", + "jsdom": "^20.0.0" + } + }, + "jest-environment-node": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.0.2.tgz", + "integrity": "sha512-4Fv8GXVCToRlMzDO94gvA8iOzKxQ7rhAbs8L+j8GPyTxGuUiYkV+63LecGeVdVhsL2KXih1sKnoqmH6tp89J7Q==", + "dev": true, + "requires": { + "@jest/environment": "^29.0.2", + "@jest/fake-timers": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "jest-mock": "^29.0.2", + "jest-util": "^29.0.2" + } + }, + "jest-get-type": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.0.0.tgz", + "integrity": "sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw==", + "dev": true + }, + "jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-leak-detector": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.0.2.tgz", + "integrity": "sha512-5f0493qDeAxjUldkBSQg5D1cLadRgZVyWpTQvfJeQwQUpHQInE21AyVHVv64M7P2Ue8Z5EZ4BAcoDS/dSPPgMw==", + "dev": true, + "requires": { + "jest-get-type": "^29.0.0", + "pretty-format": "^29.0.2" + } + }, + "jest-matcher-utils": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.0.2.tgz", + "integrity": "sha512-s62YkHFBfAx0JLA2QX1BlnCRFwHRobwAv2KP1+YhjzF6ZCbCVrf1sG8UJyn62ZUsDaQKpoo86XMTjkUyO5aWmQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.0.2", + "jest-get-type": "^29.0.0", + "pretty-format": "^29.0.2" + } + }, + "jest-message-util": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.2.tgz", + "integrity": "sha512-kcJAgms3ckJV0wUoLsAM40xAhY+pb9FVSZwicjFU9PFkaTNmqh9xd99/CzKse48wPM1ANUQKmp03/DpkY+lGrA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.0.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-mock": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.0.2.tgz", + "integrity": "sha512-giWXOIT23UCxHCN2VUfUJ0Q7SmiqQwfSFXlCaIhW5anITpNQ+3vuLPQdKt5wkuwM37GrbFyHIClce8AAK9ft9g==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "@types/node": "*" + } + }, + "jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "requires": {} + }, + "jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true + }, + "jest-resolve": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.0.2.tgz", + "integrity": "sha512-V3uLjSA+EHxLtjIDKTBXnY71hyx+8lusCqPXvqzkFO1uCGvVpjBfuOyp+KOLBNSuY61kM2jhepiMwt4eiJS+Vw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.0.2", + "jest-validate": "^29.0.2", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + } + }, + "jest-resolve-dependencies": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.0.2.tgz", + "integrity": "sha512-fSAu6eIG7wtGdnPJUkVVdILGzYAP9Dj/4+zvC8BrGe8msaUMJ9JeygU0Hf9+Uor6/icbuuzQn5See1uajLnAqg==", + "dev": true, + "requires": { + "jest-regex-util": "^29.0.0", + "jest-snapshot": "^29.0.2" + } + }, + "jest-runner": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.0.2.tgz", + "integrity": "sha512-+D82iPZejI8t+SfduOO1deahC/QgLFf8aJBO++Znz3l2ETtOMdM7K4ATsGWzCFnTGio5yHaRifg1Su5Ybza5Nw==", + "dev": true, + "requires": { + "@jest/console": "^29.0.2", + "@jest/environment": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.0.0", + "jest-environment-node": "^29.0.2", + "jest-haste-map": "^29.0.2", + "jest-leak-detector": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-resolve": "^29.0.2", + "jest-runtime": "^29.0.2", + "jest-util": "^29.0.2", + "jest-watcher": "^29.0.2", + "jest-worker": "^29.0.2", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + } + }, + "jest-runtime": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.0.2.tgz", + "integrity": "sha512-DO6F81LX4okOgjJLkLySv10E5YcV5NHUbY1ZqAUtofxdQE+q4hjH0P2gNsY8x3z3sqgw7O/+919SU4r18Fcuig==", + "dev": true, + "requires": { + "@jest/environment": "^29.0.2", + "@jest/fake-timers": "^29.0.2", + "@jest/globals": "^29.0.2", + "@jest/source-map": "^29.0.0", + "@jest/test-result": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-mock": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-resolve": "^29.0.2", + "jest-snapshot": "^29.0.2", + "jest-util": "^29.0.2", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + } + }, + "jest-snapshot": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.0.2.tgz", + "integrity": "sha512-26C4PzGKaX5gkoKg8UzYGVy2HPVcTaROSkf0gwnHu3lGeTB7bAIJBovvVPZoiJ20IximJELQs/r8WSDRCuGX2A==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.0.2", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.0.2", + "jest-get-type": "^29.0.0", + "jest-haste-map": "^29.0.2", + "jest-matcher-utils": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-util": "^29.0.2", + "natural-compare": "^1.4.0", + "pretty-format": "^29.0.2", + "semver": "^7.3.5" + } + }, + "jest-util": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.2.tgz", + "integrity": "sha512-ozk8ruEEEACxqpz0hN9UOgtPZS0aN+NffwQduR5dVlhN+eN47vxurtvgZkYZYMpYrsmlAEx1XabkB3BnN0GfKQ==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "jest-validate": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.0.2.tgz", + "integrity": "sha512-AeRKm7cEucSy7tr54r3LhiGIXYvOILUwBM1S7jQkKs6YelwAlWKsmZGVrQR7uwsd31rBTnR5NQkODi1Z+6TKIQ==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.0.0", + "leven": "^3.1.0", + "pretty-format": "^29.0.2" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + } + } + }, + "jest-watcher": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.0.2.tgz", + "integrity": "sha512-ds2bV0oyUdYoyrUTv4Ga5uptz4cEvmmP/JzqDyzZZanvrIn8ipxg5l3SDOAIiyuAx1VdHd2FBzeXPFO5KPH8vQ==", + "dev": true, + "requires": { + "@jest/test-result": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^29.0.2", + "string-length": "^4.0.1" + } + }, + "jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "joi": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz", + "integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, + "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 + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsdom": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", + "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "acorn": "^8.7.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "^7.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^3.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.8.0", + "xml-name-validator": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "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 + }, + "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 + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "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 + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "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, + "requires": { + "yallist": "^4.0.0" + } + }, + "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, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "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 + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "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 + }, + "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, + "requires": { + "mime-db": "1.52.0" + } + }, + "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 + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true + }, + "node-fetch": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", + "dev": true, + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + } + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "path-key": "^3.0.0" + } + }, + "nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", + "dev": true + }, + "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, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "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 + }, + "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, + "requires": { + "callsites": "^3.0.0" + } + }, + "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, + "requires": { + "@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" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true + }, + "parse5": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.1.tgz", + "integrity": "sha512-kwpuwzB+px5WUg9pyK0IcK/shltJN5/OVhQagxhCQNtT9Y9QRZqNY2e1cmbu/paRh5LMnz/oVTVLBpjFmMZhSg==", + "dev": true, + "requires": { + "entities": "^4.4.0" + } + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true + }, + "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, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "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, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "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, + "requires": { + "p-locate": "^4.1.0" + } + }, + "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, + "requires": { + "p-try": "^2.0.0" + } + }, + "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, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "pretty-format": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.2.tgz", + "integrity": "sha512-wp3CdtUa3cSJVFn3Miu5a1+pxc1iPIQTenOAn+x5erXeN1+ryTcLesV5pbK/rlW5EKwp27x38MoYfNGaNXDDhg==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "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 + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "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 + }, + "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 + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "resolve-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", + "integrity": "sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==", + "dev": true, + "requires": { + "expand-tilde": "^1.2.2", + "global-modules": "^0.2.3" + } + }, + "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 + }, + "resolve.exports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", + "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "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, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rxjs": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", + "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + } + } + }, + "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 + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "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, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "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 + }, + "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 + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "spawnd": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-6.0.2.tgz", + "integrity": "sha512-+YJtx0dvy2wt304MrHD//tASc84zinBUYU1jacPBzrjhZUd7RsDo25krxr4HUHAQzEQFuMAs4/p+yLYU5ciZ1w==", + "dev": true, + "requires": { + "exit": "^0.1.2", + "signal-exit": "^3.0.6", + "tree-kill": "^1.2.2" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "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, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "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 + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "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, + "requires": { + "is-number": "^7.0.0" + } + }, + "tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + } + }, + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "typescript": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz", + "integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==", + "dev": true, + "peer": true + }, + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz", + "integrity": "sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "v8-to-istanbul": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", + "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + } + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", + "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", + "dev": true, + "requires": { + "xml-name-validator": "^4.0.0" + } + }, + "wait-on": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-6.0.1.tgz", + "integrity": "sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==", + "dev": true, + "requires": { + "axios": "^0.25.0", + "joi": "^17.6.0", + "lodash": "^4.17.21", + "minimist": "^1.2.5", + "rxjs": "^7.5.4" + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "dev": true + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + } + }, + "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 + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "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 + }, + "write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "dev": true, + "requires": {} + }, + "xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..8e46442 --- /dev/null +++ b/client/package.json @@ -0,0 +1,18 @@ +{ + "name": "webclient", + "version": "3.1.0", + "private": true, + "scripts": { + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", + "lint": "eslint public/**/*.js src/**/*.js test/**/*.js" + }, + "devDependencies": { + "eslint": "^8.23.0", + "eslint-plugin-jest": "^27.0.1", + "jest": "^29.0.2", + "jest-dev-server": "^6.1.1", + "jest-environment-jsdom": "^29.0.2", + "node-fetch": "^3.2.10" + }, + "type": "module" +} diff --git a/client/public/bidirectional/css/style.css b/client/public/bidirectional/css/style.css new file mode 100644 index 0000000..b5617d6 --- /dev/null +++ b/client/public/bidirectional/css/style.css @@ -0,0 +1,54 @@ +div#select, div#resolution { + margin: 1em; +} + +button { + margin: 0 20px 5px 0; + vertical-align: top; + width: 155px; +} + +div#buttons { + border-top: 1px solid #eee; + border-bottom: 1px solid #eee; + margin: 1em 0 1em 0; + padding: 1em 0 1em 0; +} + +div#local { + margin: 0 20px 0 0; +} + +div#preview { + border-bottom: 1px solid #eee; + margin: 0 0 1em 0; + padding: 0 0 0.5em 0; +} + +div#preview>div { + display: inline-block; + vertical-align: top; + width: calc(50% - 20px); +} + +div#connectionId { + margin: 1em; +} + +h2 { + margin: 0 0 0.5em 0; +} + +textarea { + color: #444; + font-size: 0.9em; + font-weight: 300; + width: calc(20% - 10px); + height: 1.3em; + line-height: 1.3; + vertical-align: middle; +} + +video { + height: 225px; +} \ No newline at end of file diff --git a/client/public/bidirectional/index.html b/client/public/bidirectional/index.html new file mode 100644 index 0000000..84dad5e --- /dev/null +++ b/client/public/bidirectional/index.html @@ -0,0 +1,83 @@ + + + + + + + + + Bidirectional Sample + + + +
+

Bidirectional Sample

+ + + +
+ + + + +
+ +
+ +
+
+ + +
+ +
+ + + +
+ +
+
+

Local

+ +
+
+
+

Remote

+ +
+
+
+ +
+ Connection ID: + +
+
+ Codec preferences: + +
+ +

For more information about Bidirectional sample, see Bidirectional + sample document page.

+ +
+ +
+ View source on GitHub +
+
+ + + + + + + + + + diff --git a/client/public/bidirectional/js/main.js b/client/public/bidirectional/js/main.js new file mode 100644 index 0000000..2e4df80 --- /dev/null +++ b/client/public/bidirectional/js/main.js @@ -0,0 +1,382 @@ +/** + * 双向视频通话应用主文件 + * 负责初始化视频设备、建立WebRTC连接、处理信令和显示视频流 + */ + +// 导入必要的模块 +import { SendVideo } from "./sendvideo.js"; // 视频发送和接收处理 +import { getServerConfig, getRTCConfiguration } from "../../js/config.js"; // 服务器配置和RTC配置 +import { createDisplayStringArray } from "../../js/stats.js"; // 统计信息处理 +import { RenderStreaming } from "../../module/renderstreaming.js"; // WebRTC连接管理 +import { Signaling, WebSocketSignaling } from "../../module/signaling.js"; // 信令管理 + +// 默认视频流尺寸 +const defaultStreamWidth = 1280; +const defaultStreamHeight = 720; + +// 预定义的视频分辨率列表 +const streamSizeList = + [ + { width: 640, height: 360 }, // 标清 + { width: 1280, height: 720 }, // 高清 + { width: 1920, height: 1080 }, // 全高清 + { width: 2560, height: 1440 }, // 2K + { width: 3840, height: 2160 }, // 4K + { width: 360, height: 640 }, // 竖屏标清 + { width: 720, height: 1280 }, // 竖屏高清 + { width: 1080, height: 1920 }, // 竖屏全高清 + { width: 1440, height: 2560 }, // 竖屏2K + { width: 2160, height: 3840 }, // 竖屏4K + ]; + +// DOM元素引用 +const localVideo = document.getElementById('localVideo'); // 本地视频元素 +const remoteVideo = document.getElementById('remoteVideo'); // 远程视频元素 +const localVideoStatsDiv = document.getElementById('localVideoStats'); // 本地视频统计信息 +const remoteVideoStatsDiv = document.getElementById('remoteVideoStats'); // 远程视频统计信息 +const textForConnectionId = document.getElementById('textForConnectionId'); // 连接ID输入框 +textForConnectionId.value = getRandom(); // 生成随机连接ID +const videoSelect = document.querySelector('select#videoSource'); // 视频设备选择 +const audioSelect = document.querySelector('select#audioSource'); // 音频设备选择 +const videoResolutionSelect = document.querySelector('select#videoResolution'); // 视频分辨率选择 +const cameraWidthInput = document.querySelector('input#cameraWidth'); // 自定义宽度输入 +const cameraHeightInput = document.querySelector('input#cameraHeight'); // 自定义高度输入 + +// 编解码器偏好设置 +const codecPreferences = document.getElementById('codecPreferences'); +// 检查浏览器是否支持设置编解码器偏好 +const supportsSetCodecPreferences = window.RTCRtpTransceiver && + 'setCodecPreferences' in window.RTCRtpTransceiver.prototype; +const messageDiv = document.getElementById('message'); // 消息显示区域 +messageDiv.style.display = 'none'; // 初始隐藏消息区域 + +let useCustomResolution = false; // 是否使用自定义分辨率 + +// 初始化输入选择和编解码器选择 +setUpInputSelect(); +showCodecSelect(); + +/** @type {SendVideo} */ +let sendVideo = new SendVideo(localVideo, remoteVideo); // 视频处理实例 +/** @type {RenderStreaming} */ +let renderstreaming; // WebRTC连接管理实例 +let useWebSocket; // 是否使用WebSocket信令 +let connectionId; // 连接ID + +// 按钮事件绑定 +const startButton = document.getElementById('startVideoButton'); +startButton.addEventListener('click', startVideo); // 启动视频按钮 +const setupButton = document.getElementById('setUpButton'); +setupButton.addEventListener('click', setUp); // 设置连接按钮 +const hangUpButton = document.getElementById('hangUpButton'); +hangUpButton.addEventListener('click', hangUp); // 挂断按钮 + +// 页面卸载前清理 +window.addEventListener('beforeunload', async () => { + if(!renderstreaming) + return; + await renderstreaming.stop(); // 停止WebRTC连接 +}, true); + +// 初始化配置 +setupConfig(); + +/** + * 初始化服务器配置 + * @async + * @returns {Promise} + */ +async function setupConfig() { + const res = await getServerConfig(); // 获取服务器配置 + useWebSocket = res.useWebSocket; // 设置是否使用WebSocket + showWarningIfNeeded(res.startupMode); // 显示启动模式警告 +} + +/** + * 根据启动模式显示警告信息 + * @param {string} startupMode - 启动模式,可能的值包括"public"和"private" + */ +function showWarningIfNeeded(startupMode) { + const warningDiv = document.getElementById("warning"); + if (startupMode == "public") { + warningDiv.innerHTML = "

Warning

This sample is not working on Public Mode."; + warningDiv.hidden = false; + } +} + +/** + * 启动本地视频 + * @async + * @returns {Promise} + */ +async function startVideo() { + // 禁用相关输入控件 + videoSelect.disabled = true; + audioSelect.disabled = true; + videoResolutionSelect.disabled = true; + cameraWidthInput.disabled = true; + cameraHeightInput.disabled = true; + startButton.disabled = true; + + let width = 0; + let height = 0; + + // 根据选择的分辨率设置视频尺寸 + if (useCustomResolution) { + width = cameraWidthInput.value ? cameraWidthInput.value : defaultStreamWidth; + height = cameraHeightInput.value ? cameraHeightInput.value : defaultStreamHeight; + } else { + const size = streamSizeList[videoResolutionSelect.value]; + width = size.width; + height = size.height; + } + + // 启动本地视频 + await sendVideo.startLocalVideo(videoSelect.value, audioSelect.value, width, height); + + // 启用设置按钮 + setupButton.disabled = false; +} + +/** + * 设置WebRTC连接 + * @async + * @returns {Promise} + */ +async function setUp() { + setupButton.disabled = true; // 禁用设置按钮 + hangUpButton.disabled = false; // 启用挂断按钮 + connectionId = textForConnectionId.value; // 获取连接ID + codecPreferences.disabled = true; // 禁用编解码器选择 + + // 创建信令实例 + const signaling = useWebSocket ? new WebSocketSignaling() : new Signaling(); + const config = getRTCConfiguration(); // 获取RTC配置 + renderstreaming = new RenderStreaming(signaling, config); // 创建WebRTC连接管理实例 + + // 连接建立回调 + renderstreaming.onConnect = () => { + const tracks = sendVideo.getLocalTracks(); // 获取本地媒体轨道 + for (const track of tracks) { + renderstreaming.addTransceiver(track, { direction: 'sendonly' }); // 添加发送轨道 + } + setCodecPreferences(); // 设置编解码器偏好 + showStatsMessage(); // 显示统计信息 + }; + + // 连接断开回调 + renderstreaming.onDisconnect = () => { + hangUp(); // 挂断连接 + }; + + // 轨道事件回调 + renderstreaming.onTrackEvent = (data) => { + const direction = data.transceiver.direction; + if (direction == "sendrecv" || direction == "recvonly") { + sendVideo.addRemoteTrack(data.track); // 添加远程轨道 + } + }; + + // 启动WebRTC连接 + await renderstreaming.start(); + await renderstreaming.createConnection(connectionId); + +} + +/** + * 设置编解码器偏好 + */ +function setCodecPreferences() { + /** @type {RTCRtpCodecCapability[] | null} */ + let selectedCodecs = null; + + if (supportsSetCodecPreferences) { + const preferredCodec = codecPreferences.options[codecPreferences.selectedIndex]; + if (preferredCodec.value !== '') { + const [mimeType, sdpFmtpLine] = preferredCodec.value.split(' '); + const { codecs } = RTCRtpSender.getCapabilities('video'); + const selectedCodecIndex = codecs.findIndex(c => c.mimeType === mimeType && c.sdpFmtpLine === sdpFmtpLine); + const selectCodec = codecs[selectedCodecIndex]; + selectedCodecs = [selectCodec]; + } + } + + if (selectedCodecs == null) { + return; + } + + // 获取视频收发器并设置编解码器偏好 + const transceivers = renderstreaming.getTransceivers().filter(t => t.receiver.track.kind == "video"); + if (transceivers && transceivers.length > 0) { + transceivers.forEach(t => t.setCodecPreferences(selectedCodecs)); + } +} + +/** + * 挂断WebRTC连接 + * @async + * @returns {Promise} + */ +async function hangUp() { + clearStatsMessage(); // 清除统计信息 + messageDiv.style.display = 'block'; + messageDiv.innerText = `Disconnect peer on ${connectionId}.`; + + hangUpButton.disabled = true; // 禁用挂断按钮 + setupButton.disabled = false; // 启用设置按钮 + + // 删除连接并停止WebRTC + await renderstreaming.deleteConnection(); + await renderstreaming.stop(); + renderstreaming = null; + remoteVideo.srcObject = null; // 清除远程视频源 + + textForConnectionId.value = getRandom(); // 生成新的随机连接ID + connectionId = null; + + // 启用编解码器选择 + if (supportsSetCodecPreferences) { + codecPreferences.disabled = false; + } +} + +/** + * 生成随机连接ID + * @returns {string} 5位随机数字字符串 + */ +function getRandom() { + const max = 99999; + const length = String(max).length; + const number = Math.floor(Math.random() * max); + return (Array(length).join('0') + number).slice(-length); // 补零确保5位 +} + +/** + * 设置输入选择控件 + * @async + * @returns {Promise} + */ +async function setUpInputSelect() { + // 获取媒体设备列表 + const deviceInfos = await navigator.mediaDevices.enumerateDevices(); + + // 填充视频设备选择 + for (let i = 0; i !== deviceInfos.length; ++i) { + const deviceInfo = deviceInfos[i]; + if (deviceInfo.kind === 'videoinput') { + const option = document.createElement('option'); + option.value = deviceInfo.deviceId; + option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`; + videoSelect.appendChild(option); + } else if (deviceInfo.kind === 'audioinput') { + // 填充音频设备选择 + const option = document.createElement('option'); + option.value = deviceInfo.deviceId; + option.text = deviceInfo.label || `mic ${audioSelect.length + 1}`; + audioSelect.appendChild(option); + } + } + + // 填充视频分辨率选择 + for (let i = 0; i < streamSizeList.length; i++) { + const streamSize = streamSizeList[i]; + const option = document.createElement('option'); + option.value = i; + option.text = `${streamSize.width} x ${streamSize.height}`; + videoResolutionSelect.appendChild(option); + } + + // 添加自定义分辨率选项 + const option = document.createElement('option'); + option.value = streamSizeList.length; + option.text = 'Custom'; + videoResolutionSelect.appendChild(option); + videoResolutionSelect.value = 1; // 默认选择1280 x 720 + + // 分辨率选择变化事件 + videoResolutionSelect.addEventListener('change', (event) => { + const isCustom = event.target.value >= streamSizeList.length; + cameraWidthInput.disabled = !isCustom; + cameraHeightInput.disabled = !isCustom; + useCustomResolution = isCustom; + }); +} + +/** + * 显示编解码器选择 + */ +function showCodecSelect() { + if (!supportsSetCodecPreferences) { + messageDiv.style.display = 'block'; + messageDiv.innerHTML = `Current Browser does not support RTCRtpTransceiver.setCodecPreferences.`; + return; + } + + // 获取视频编解码器能力 + const codecs = RTCRtpSender.getCapabilities('video').codecs; + codecs.forEach(codec => { + // 跳过冗余和FEC编解码器 + if (['video/red', 'video/ulpfec', 'video/rtx'].includes(codec.mimeType)) { + return; + } + const option = document.createElement('option'); + option.value = (codec.mimeType + ' ' + (codec.sdpFmtpLine || '')).trim(); + option.innerText = option.value; + codecPreferences.appendChild(option); + }); + codecPreferences.disabled = false; +} + +// 统计信息相关变量 +let lastStats; // 上次统计信息 +let intervalId; // 统计信息更新间隔ID + +/** + * 显示统计信息 + */ +function showStatsMessage() { + // 每秒更新一次统计信息 + intervalId = setInterval(async () => { + // 显示本地视频分辨率 + if (localVideo.videoWidth) { + localVideoStatsDiv.innerHTML = `Sending resolution: ${localVideo.videoWidth} x ${localVideo.videoHeight} px`; + } + // 显示远程视频分辨率 + if (remoteVideo.videoWidth) { + remoteVideoStatsDiv.innerHTML = `Receiving resolution: ${remoteVideo.videoWidth} x ${remoteVideo.videoHeight} px`; + } + + if (renderstreaming == null || connectionId == null) { + return; + } + + // 获取WebRTC统计信息 + const stats = await renderstreaming.getStats(); + if (stats == null) { + return; + } + + // 创建统计信息显示数组 + const array = createDisplayStringArray(stats, lastStats); + if (array.length) { + messageDiv.style.display = 'block'; + messageDiv.innerHTML = array.join('
'); + } + lastStats = stats; + }, 1000); +} + +/** + * 清除统计信息 + */ +function clearStatsMessage() { + if (intervalId) { + clearInterval(intervalId); // 清除定时器 + } + lastStats = null; + intervalId = null; + localVideoStatsDiv.innerHTML = ''; + remoteVideoStatsDiv.innerHTML = ''; + messageDiv.style.display = 'none'; + messageDiv.innerHTML = ''; +} diff --git a/client/public/bidirectional/js/sendvideo.js b/client/public/bidirectional/js/sendvideo.js new file mode 100644 index 0000000..8cea080 --- /dev/null +++ b/client/public/bidirectional/js/sendvideo.js @@ -0,0 +1,53 @@ +import * as Logger from "../../module/logger.js"; + +export class SendVideo { + constructor(localVideoElement, remoteVideoElement) { + this.localVideo = localVideoElement; + this.remoteVideo = remoteVideoElement; + } + + /** + * @param {MediaTrackConstraints} videoSource + * @param {MediaTrackConstraints} audioSource + * @param {number} videoWidth + * @param {number} videoHeight + */ + async startLocalVideo(videoSource, audioSource, videoWidth, videoHeight) { + try { + const constraints = { + video: { deviceId: videoSource ? { exact: videoSource } : undefined }, + audio: { deviceId: audioSource ? { exact: audioSource } : undefined } + }; + + if (videoWidth != null || videoWidth != 0) { + constraints.video.width = videoWidth; + } + if (videoHeight != null || videoHeight != 0) { + constraints.video.height = videoHeight; + } + + const localStream = await navigator.mediaDevices.getUserMedia(constraints); + this.localVideo.srcObject = localStream; + await this.localVideo.play(); + } catch (err) { + Logger.error(`mediaDevice.getUserMedia() error:${err}`); + } + } + + /** + * @returns {MediaStreamTrack[]} + */ + getLocalTracks() { + return this.localVideo.srcObject.getTracks(); + } + + /** + * @param {MediaStreamTrack} track + */ + addRemoteTrack(track) { + if (this.remoteVideo.srcObject == null) { + this.remoteVideo.srcObject = new MediaStream(); + } + this.remoteVideo.srcObject.addTrack(track); + } +} diff --git a/client/public/css/main.css b/client/public/css/main.css new file mode 100644 index 0000000..28f66a2 --- /dev/null +++ b/client/public/css/main.css @@ -0,0 +1,147 @@ +h1 { + border-bottom: 1px solid #ccc; + font-weight: 500; + margin: 0 0 0.8em 0; + padding: 0 0 0.2em 0; +} + +h4 { + margin: 0; + padding: 0 0 0.2em 0; +} + +body { + font-family: 'Roboto', sans-serif; + font-weight: 300; + margin: 0; + padding: 1em; + word-break: break-word; +} + +button { + margin: 20px 10px 0 0; + width: 130px; +} + +button#gather { + display: block; +} + +section { + border-bottom: 1px solid #eee; + margin: 0 0 1.5em 0; + padding: 0 0 1.5em 0; +} + +section#iceServers label { + display: inline-block; + width: 150px; +} + +section#iceServers input { + margin: 0 0 10px; + width: 260px; +} + +select { + margin: 0 1em 1em 0; + position: relative; + top: -1px; +} + +select#servers { + font-size: 1em; + padding: 5px; + width: 420px; +} + +section:last-child { + border-bottom: none; + margin: 0; + padding: 0; +} + +div#container { + margin: 0 auto 0 auto; + max-width: 60em; + padding: 1em 1.5em 1.3em 1.5em; +} + +code { + padding: 0.1em 0.25em; + color: #444; + background-color: #e7edf3; + border-radius: 3px; + border: solid 1px #d6dde4; + font-weight: 400; +} + +p { + color: #444; + font-weight: 300; +} + +p#data { + border-top: 1px dotted #666; + line-height: 1.3em; + max-height: 1000px; + overflow-y: auto; + padding: 1em 0 0 0; +} + +p.borderBelow { + border-bottom: 1px solid #aaa; + padding: 0 0 20px 0; +} + +video { + background: #222; + margin: 0 0 20px 0; + --width: 100%; + width: var(--width); + height: calc(var(--width) * 0.75); +} + +div#warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; + padding: 1em; + border: 1px solid transparent; +} + +div.box { + margin: 1em; +} + +div#message { + border-top: 1px solid #666; + margin: 1em; + padding: 1em; +} + +@media screen and (max-width: 650px) { + .highlight { + font-size: 1em; + margin: 0 0 20px 0; + padding: 0.2em 1em; + } + h1 { + font-size: 24px; + } +} + +@media screen and (max-width: 550px) { + button:active { + background-color: darkRed; + } + h1 { + font-size: 22px; + } +} + +@media screen and (max-width: 450px) { + h1 { + font-size: 20px; + } +} \ No newline at end of file diff --git a/client/public/images/FullScreen.png b/client/public/images/FullScreen.png new file mode 100644 index 0000000..ba386e6 Binary files /dev/null and b/client/public/images/FullScreen.png differ diff --git a/client/public/images/Play.png b/client/public/images/Play.png new file mode 100644 index 0000000..9d8dbd1 Binary files /dev/null and b/client/public/images/Play.png differ diff --git a/client/public/images/favicon.ico b/client/public/images/favicon.ico new file mode 100644 index 0000000..bd5b982 Binary files /dev/null and b/client/public/images/favicon.ico differ diff --git a/client/public/images/p1.png b/client/public/images/p1.png new file mode 100644 index 0000000..bf2c348 Binary files /dev/null and b/client/public/images/p1.png differ diff --git a/client/public/images/p2.png b/client/public/images/p2.png new file mode 100644 index 0000000..dac4c6a Binary files /dev/null and b/client/public/images/p2.png differ diff --git a/client/public/images/screenshot.png b/client/public/images/screenshot.png new file mode 100644 index 0000000..914f009 Binary files /dev/null and b/client/public/images/screenshot.png differ diff --git a/client/public/index.html b/client/public/index.html new file mode 100644 index 0000000..b71b437 --- /dev/null +++ b/client/public/index.html @@ -0,0 +1,77 @@ + + + + + Unity Render Streaming Samples + + + +
+

Unity Render Streaming Samples

+ +
+

These are WebClient samples for use with Unity Render + Streaming.

+
+ +
+

Server Configuration

+
+
+ +
+

ICE servers

+ +
+ + +
+
+ + +
+
+ + +
+
+ + + +
+
+ +
+

Receiver Sample

+

This is a sample for receiving video / audio from Unity.

+

It can be used in combination with the Broadcast scene of Unity Render Streaming.

+
+ +
+

Bidirectional Sample

+

This is a sample for sending and receiving video in both directions.

+

It can be used in combination with the Bidirectional scene of Unity Render Streaming.

+

The WebApp must be running in Private mode.

+
+ +
+

Multiplay Sample

+

This sample connects as a Guest in the Multiplay scene of Unity Render Streaming.

+
+ +
+

VideoPlayer Sample

+

This is a sample to receive the camera image rendered on Unity. You can operate the camera in Unity from the + browser.

+

It can be used in combination with the WebBrowserInput scene of Unity Render Streaming.

+

The WebApp must be running in Public mode.

+
+ +
+ View source on GitHub +
+
+ + + diff --git a/client/public/js/config.js b/client/public/js/config.js new file mode 100644 index 0000000..487f439 --- /dev/null +++ b/client/public/js/config.js @@ -0,0 +1,38 @@ +import {getServers} from "./icesettings.js"; + +export async function getServerConfig() { + const protocolEndPoint = location.origin + '/config'; + const createResponse = await fetch(protocolEndPoint); + return await createResponse.json(); +} + +export function getRTCConfiguration() { + let config = {}; + config.sdpSemantics = 'unified-plan'; + config.iceServers = getServers(); + + // 添加音频处理选项,增强回声消除 + config.mediaConstraints = { + audio: { + echoCancellation: true, + noiseSuppression: true, + autoGainControl: true, + highpassFilter: true, + typingNoiseDetection: true + } + }; + + // 添加WebRTC音频处理选项 + config.rtcConfiguration = { + 'googEchoCancellation': true, + 'googEchoCancellation2': true, + 'googNoiseSuppression': true, + 'googNoiseSuppression2': true, + 'googAutoGainControl': true, + 'googAutoGainControl2': true, + 'googHighpassFilter': true, + 'googTypingNoiseDetection': true + }; + + return config; +} diff --git a/client/public/js/icesettings.js b/client/public/js/icesettings.js new file mode 100644 index 0000000..22b986f --- /dev/null +++ b/client/public/js/icesettings.js @@ -0,0 +1,103 @@ +// This code is referenced from webrtc sample. +// https://github.com/webrtc/samples/blob/gh-pages/src/content/peerconnection/trickle-ice/js/main.js + +const servers = document.querySelector('select#servers'); +const urlInput = document.querySelector('input#url'); +const usernameInput = document.querySelector('input#username'); +const passwordInput = document.querySelector('input#password'); + +const allServersKey = 'servers'; + +export function addServer() { + const scheme = urlInput.value.split(':')[0]; + if (!['stun', 'stuns', 'turn', 'turns'].includes(scheme)) { + alert(`URI scheme ${scheme} is not valid`); + return; + } + + // Store the ICE server as a stringified JSON object in option.value. + const option = document.createElement('option'); + const iceServer = { + urls: [urlInput.value], + username: usernameInput.value, + credential: passwordInput.value + }; + option.value = JSON.stringify(iceServer); + option.text = `${urlInput.value} `; + const username = usernameInput.value; + const password = passwordInput.value; + if (username || password) { + option.text += (` [${username}:${password}]`); + } + option.ondblclick = selectServer; + servers.add(option); + urlInput.value = usernameInput.value = passwordInput.value = ''; + writeServersToLocalStorage(); +} + +export function removeServer() { + for (let i = servers.options.length - 1; i >= 0; --i) { + if (servers.options[i].selected) { + servers.remove(i); + } + } + writeServersToLocalStorage(); +} + +export function reset() { + window.localStorage.clear(); + document.querySelectorAll('select#servers option').forEach(option => option.remove()); + const serversSelect = document.querySelector('select#servers'); + setDefaultServer(serversSelect); +} + +function selectServer(event) { + const option = event.target; + const value = JSON.parse(option.value); + urlInput.value = value.urls[0]; + usernameInput.value = value.username || ''; + passwordInput.value = value.credential || ''; +} + +function setDefaultServer(serversSelect) { + const option = document.createElement('option'); + option.value = '{"urls":["stun:stun.l.google.com:19302"]}'; + option.text = 'stun:stun.l.google.com:19302'; + option.ondblclick = selectServer; + serversSelect.add(option); +} + +function writeServersToLocalStorage() { + const serversSelect = document.querySelector('select#servers'); + const allServers = JSON.stringify(Object.values(serversSelect.options).map(o => JSON.parse(o.value))); + window.localStorage.setItem(allServersKey, allServers); +} + +export function readServersFromLocalStorage() { + document.querySelectorAll('select#servers option').forEach(option => option.remove()); + const serversSelect = document.querySelector('select#servers'); + const storedServers = window.localStorage.getItem(allServersKey); + + if (storedServers === null || storedServers === '') { + setDefaultServer(serversSelect); + } else { + JSON.parse(storedServers).forEach((server) => { + const o = document.createElement('option'); + o.value = JSON.stringify(server); + o.text = server.urls[0]; + o.ondblclick = selectServer; + serversSelect.add(o); + }); + } +} + +export function getServers() { + const storedServers = window.localStorage.getItem(allServersKey); + + if (storedServers === null || storedServers === '') { + return [{ urls: ['stun:stun.l.google.com:19302'] }]; + } + else { + return JSON.parse(storedServers); + } +} diff --git a/client/public/js/main.js b/client/public/js/main.js new file mode 100644 index 0000000..cdf5825 --- /dev/null +++ b/client/public/js/main.js @@ -0,0 +1,27 @@ +import * as Config from "./config.js"; +import {addServer, removeServer, reset, readServersFromLocalStorage} from "./icesettings.js"; + +const addButton = document.querySelector('button#add'); +const removeButton = document.querySelector('button#remove'); +const resetButton = document.querySelector('button#reset'); +const startupDiv = document.getElementById("startup"); + +addButton.onclick = addServer; +removeButton.onclick = removeServer; +resetButton.onclick = reset; +startupDiv.innerHTML = ""; + +const displayConfig = async () => { + const res = await Config.getServerConfig(); + if (res.useWebSocket) { + startupDiv.innerHTML += "
  • Signaling Protocol : WebSocket
  • "; + } else { + startupDiv.innerHTML += "
  • Signaling Protocol : HTTP
  • "; + } + + const mode = res.startupMode.replace(/^./, res.startupMode[0].toUpperCase()); + startupDiv.innerHTML += `
  • Signaling Mode : ${mode}
  • `; +}; + +displayConfig(); +readServersFromLocalStorage(); diff --git a/client/public/js/stats.js b/client/public/js/stats.js new file mode 100644 index 0000000..6d60671 --- /dev/null +++ b/client/public/js/stats.js @@ -0,0 +1,91 @@ +/** + * create display string array from RTCStatsReport + * @param {RTCStatsReport} report - current RTCStatsReport + * @param {RTCStatsReport} lastReport - latest RTCStatsReport + * @return {Array} - display string Array + */ +export function createDisplayStringArray(report, lastReport) { + let array = new Array(); + + report.forEach(stat => { + if (stat.type === 'inbound-rtp') { + array.push(`${stat.kind} receiving stream stats`); + + if (stat.codecId != undefined) { + const codec = report.get(stat.codecId); + array.push(`Codec: ${codec.mimeType}`); + + if (codec.sdpFmtpLine) { + codec.sdpFmtpLine.split(";").forEach(fmtp => { + array.push(` - ${fmtp}`); + }); + } + + if (codec.payloadType) { + array.push(` - payloadType=${codec.payloadType}`); + } + + if (codec.clockRate) { + array.push(` - clockRate=${codec.clockRate}`); + } + + if (codec.channels) { + array.push(` - channels=${codec.channels}`); + } + } + + if (stat.kind == "video") { + array.push(`Decoder: ${stat.decoderImplementation}`); + array.push(`Resolution: ${stat.frameWidth}x${stat.frameHeight}`); + array.push(`Framerate: ${stat.framesPerSecond}`); + } + + if (lastReport && lastReport.has(stat.id)) { + const lastStats = lastReport.get(stat.id); + const duration = (stat.timestamp - lastStats.timestamp) / 1000; + const bitrate = (8 * (stat.bytesReceived - lastStats.bytesReceived) / duration) / 1000; + array.push(`Bitrate: ${bitrate.toFixed(2)} kbit/sec`); + } + } else if (stat.type === 'outbound-rtp') { + array.push(`${stat.kind} sending stream stats`); + + if (stat.codecId != undefined) { + const codec = report.get(stat.codecId); + array.push(`Codec: ${codec.mimeType}`); + + if (codec.sdpFmtpLine) { + codec.sdpFmtpLine.split(";").forEach(fmtp => { + array.push(` - ${fmtp}`); + }); + } + + if (codec.payloadType) { + array.push(` - payloadType=${codec.payloadType}`); + } + + if (codec.clockRate) { + array.push(` - clockRate=${codec.clockRate}`); + } + + if (codec.channels) { + array.push(` - channels=${codec.channels}`); + } + } + + if (stat.kind == "video") { + array.push(`Encoder: ${stat.encoderImplementation}`); + array.push(`Resolution: ${stat.frameWidth}x${stat.frameHeight}`); + array.push(`Framerate: ${stat.framesPerSecond}`); + } + + if (lastReport && lastReport.has(stat.id)) { + const lastStats = lastReport.get(stat.id); + const duration = (stat.timestamp - lastStats.timestamp) / 1000; + const bitrate = (8 * (stat.bytesSent - lastStats.bytesSent) / duration) / 1000; + array.push(`Bitrate: ${bitrate.toFixed(2)} kbit/sec`); + } + } + }); + + return array; +} \ No newline at end of file diff --git a/client/public/js/videoplayer.js b/client/public/js/videoplayer.js new file mode 100644 index 0000000..6945540 --- /dev/null +++ b/client/public/js/videoplayer.js @@ -0,0 +1,213 @@ +import { Observer, Sender } from "../module/sender.js"; +import { InputRemoting } from "../module/inputremoting.js"; + +export class VideoPlayer { + constructor() { + this.playerElement = null; + this.lockMouseCheck = null; + this.videoElement = null; + this.fullScreenButtonElement = null; + this.inputRemoting = null; + this.sender = null; + this.inputSenderChannel = null; + } + + /** + * @param {Element} playerElement parent element for create video player + * @param {HTMLInputElement} lockMouseCheck use checked propety for lock mouse + */ + createPlayer(playerElement, lockMouseCheck) { + this.playerElement = playerElement; + this.lockMouseCheck = lockMouseCheck; + + this.videoElement = document.createElement('video'); + this.videoElement.id = 'Video'; + this.videoElement.style.touchAction = 'none'; + this.videoElement.playsInline = true; + this.videoElement.srcObject = new MediaStream(); + this.videoElement.addEventListener('loadedmetadata', this._onLoadedVideo.bind(this), true); + this.playerElement.appendChild(this.videoElement); + + // add fullscreen button + this.fullScreenButtonElement = document.createElement('img'); + this.fullScreenButtonElement.id = 'fullscreenButton'; + this.fullScreenButtonElement.src = '../images/FullScreen.png'; + this.fullScreenButtonElement.addEventListener("click", this._onClickFullscreenButton.bind(this)); + this.playerElement.appendChild(this.fullScreenButtonElement); + + document.addEventListener('webkitfullscreenchange', this._onFullscreenChange.bind(this)); + document.addEventListener('fullscreenchange', this._onFullscreenChange.bind(this)); + this.videoElement.addEventListener("click", this._mouseClick.bind(this), false); + } + + _onLoadedVideo() { + this.videoElement.play(); + this.resizeVideo(); + } + + _onClickFullscreenButton() { + if (!document.fullscreenElement || !document.webkitFullscreenElement) { + if (document.documentElement.requestFullscreen) { + document.documentElement.requestFullscreen(); + } + else if (document.documentElement.webkitRequestFullscreen) { + document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } else { + if (this.playerElement.style.position == "absolute") { + this.playerElement.style.position = "relative"; + } else { + this.playerElement.style.position = "absolute"; + } + } + } + } + + _onFullscreenChange() { + if (document.webkitFullscreenElement || document.fullscreenElement) { + this.playerElement.style.position = "absolute"; + this.fullScreenButtonElement.style.display = 'none'; + + if (this.lockMouseCheck.checked) { + if (document.webkitFullscreenElement.requestPointerLock) { + document.webkitFullscreenElement.requestPointerLock(); + } else if (document.fullscreenElement.requestPointerLock) { + document.fullscreenElement.requestPointerLock(); + } else if (document.mozFullScreenElement.requestPointerLock) { + document.mozFullScreenElement.requestPointerLock(); + } + + // Subscribe to events + document.addEventListener('mousemove', this._mouseMove.bind(this), false); + document.addEventListener('click', this._mouseClickFullScreen.bind(this), false); + } + } + else { + this.playerElement.style.position = "relative"; + this.fullScreenButtonElement.style.display = 'block'; + + document.removeEventListener('mousemove', this._mouseMove.bind(this), false); + document.removeEventListener('click', this._mouseClickFullScreen.bind(this), false); + } + } + + _mouseMove(event) { + // Forward mouseMove event of fullscreen player directly to sender + // This is required, as the regular mousemove event doesn't fire when in fullscreen mode + this.sender._onMouseEvent(event); + } + + _mouseClick() { + // Restores pointer lock when we unfocus the player and click on it again + if (this.lockMouseCheck.checked) { + if (this.videoElement.requestPointerLock) { + this.videoElement.requestPointerLock().catch(function () { }); + } + } + } + + _mouseClickFullScreen() { + // Restores pointer lock when we unfocus the fullscreen player and click on it again + if (this.lockMouseCheck.checked) { + if (document.webkitFullscreenElement.requestPointerLock) { + document.webkitFullscreenElement.requestPointerLock(); + } else if (document.fullscreenElement.requestPointerLock) { + document.fullscreenElement.requestPointerLock(); + } else if (document.mozFullScreenElement.requestPointerLock) { + document.mozFullScreenElement.requestPointerLock(); + } + } + } + + /** + * @param {MediaStreamTrack} track + */ + addTrack(track) { + if (!this.videoElement.srcObject) { + return; + } + + this.videoElement.srcObject.addTrack(track); + } + + resizeVideo() { + if (!this.videoElement) { + return; + } + + const clientRect = this.videoElement.getBoundingClientRect(); + const videoRatio = this.videoWidth / this.videoHeight; + const clientRatio = clientRect.width / clientRect.height; + + this._videoScale = videoRatio > clientRatio ? clientRect.width / this.videoWidth : clientRect.height / this.videoHeight; + const videoOffsetX = videoRatio > clientRatio ? 0 : (clientRect.width - this.videoWidth * this._videoScale) * 0.5; + const videoOffsetY = videoRatio > clientRatio ? (clientRect.height - this.videoHeight * this._videoScale) * 0.5 : 0; + this._videoOriginX = clientRect.left + videoOffsetX; + this._videoOriginY = clientRect.top + videoOffsetY; + } + + get videoWidth() { + return this.videoElement.videoWidth; + } + + get videoHeight() { + return this.videoElement.videoHeight; + } + + get videoOriginX() { + return this._videoOriginX; + } + + get videoOriginY() { + return this._videoOriginY; + } + + get videoScale() { + return this._videoScale; + } + + deletePlayer() { + if (this.inputRemoting) { + this.inputRemoting.stopSending(); + } + this.inputRemoting = null; + this.sender = null; + this.inputSenderChannel = null; + + while (this.playerElement.firstChild) { + this.playerElement.removeChild(this.playerElement.firstChild); + } + + this.playerElement = null; + this.lockMouseCheck = null; + } + + _isTouchDevice() { + return (('ontouchstart' in window) || + (navigator.maxTouchPoints > 0) || + (navigator.msMaxTouchPoints > 0)); + } + + /** + * setup datachannel for player input (muouse/keyboard/touch/gamepad) + * @param {RTCDataChannel} channel + */ + setupInput(channel) { + this.sender = new Sender(this.videoElement); + this.sender.addMouse(); + this.sender.addKeyboard(); + if (this._isTouchDevice()) { + this.sender.addTouchscreen(); + } + this.sender.addGamepad(); + this.inputRemoting = new InputRemoting(this.sender); + + this.inputSenderChannel = channel; + this.inputSenderChannel.onopen = this._onOpenInputSenderChannel.bind(this); + this.inputRemoting.subscribe(new Observer(this.inputSenderChannel)); + } + + async _onOpenInputSenderChannel() { + await new Promise(resolve => setTimeout(resolve, 100)); + this.inputRemoting.startSending(); + } +} \ No newline at end of file diff --git a/client/public/multiplay/css/style.css b/client/public/multiplay/css/style.css new file mode 100644 index 0000000..73ce19f --- /dev/null +++ b/client/public/multiplay/css/style.css @@ -0,0 +1,99 @@ +body { + margin: 0px; +} + +#player { + position: relative; + top: 0; + right: 0; + bottom: 0; + left: 0; + align-items: center; + justify-content: center; + display: flex; + background-color: #323232; +} + +#player:before { + content: ""; + display: block; + padding-top: 66%; +} + +#playButton { + width: 15%; + max-width: 200px; + cursor: pointer; +} + +#Video { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +#VideoThumbnail { + position: absolute; + top: 0; + left: 0; + width: 30%; + height: 30%; +} + +#greenButton { + position: absolute; + bottom: 10px; + left: 10px; + width: 160px; + background-color: #4CAF50; + /* Green */ + border: none; + color: white; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; +} + +#blueButton { + position: absolute; + bottom: 10px; + left: 180px; + width: 160px; + background-color: #447FAF; + /* Blue */ + border: none; + color: white; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; +} + +#orangeButton { + position: absolute; + bottom: 10px; + left: 350px; + width: 160px; + background-color: #FF7700; + /* Blue */ + border: none; + color: white; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; +} + +#fullscreenButton { + position: absolute; + top: 25px; + right: 25px; + width: 32px; + height: 32px; +} \ No newline at end of file diff --git a/client/public/multiplay/index.html b/client/public/multiplay/index.html new file mode 100644 index 0000000..7cbf3d5 --- /dev/null +++ b/client/public/multiplay/index.html @@ -0,0 +1,53 @@ + + + + + + + + + Multiplay Sample + + + + +
    +

    Multiplay Sample

    + + + +
    + +
    + Codec preferences: + +
    + +
    + Lock Cursor to Player: + +
    + +

    + For more information about sample, see Multiplay sample document page. +

    + +
    + +
    + View source on GitHub +
    +
    + + + + + + + + + diff --git a/client/public/multiplay/js/main.js b/client/public/multiplay/js/main.js new file mode 100644 index 0000000..b9f147c --- /dev/null +++ b/client/public/multiplay/js/main.js @@ -0,0 +1,203 @@ +import { getServerConfig, getRTCConfiguration } from "../../js/config.js"; +import { createDisplayStringArray } from "../../js/stats.js"; +import { VideoPlayer } from "../../js/videoplayer.js"; +import { RenderStreaming } from "../../module/renderstreaming.js"; +import { Signaling, WebSocketSignaling } from "../../module/signaling.js"; + +/** @enum {number} */ +const ActionType = { + ChangeLabel: 0 +}; + +/** @type {Element} */ +let playButton; +/** @type {RenderStreaming} */ +let renderstreaming; +/** @type {boolean} */ +let useWebSocket; +/** @type {RTCDataChannel} */ +let multiplayChannel; + +const codecPreferences = document.getElementById('codecPreferences'); +const supportsSetCodecPreferences = window.RTCRtpTransceiver && + 'setCodecPreferences' in window.RTCRtpTransceiver.prototype; +const messageDiv = document.getElementById('message'); +messageDiv.style.display = 'none'; + +const playerDiv = document.getElementById('player'); +const lockMouseCheck = document.getElementById('lockMouseCheck'); +const videoPlayer = new VideoPlayer(); + +setup(); + +window.document.oncontextmenu = function () { + return false; // cancel default menu +}; + +window.addEventListener('resize', function () { + videoPlayer.resizeVideo(); +}, true); + +window.addEventListener('beforeunload', async () => { + if(!renderstreaming) + return; + await renderstreaming.stop(); +}, true); + +async function setup() { + const res = await getServerConfig(); + useWebSocket = res.useWebSocket; + showWarningIfNeeded(res.startupMode); + showCodecSelect(); + showPlayButton(); +} + +function showWarningIfNeeded(startupMode) { + const warningDiv = document.getElementById("warning"); + if (startupMode == "private") { + warningDiv.innerHTML = "

    Warning

    This sample is not working on Private Mode."; + warningDiv.hidden = false; + } +} + +function showPlayButton() { + if (!document.getElementById('playButton')) { + const elementPlayButton = document.createElement('img'); + elementPlayButton.id = 'playButton'; + elementPlayButton.src = '../../images/Play.png'; + elementPlayButton.alt = 'Start Streaming'; + playButton = document.getElementById('player').appendChild(elementPlayButton); + playButton.addEventListener('click', onClickPlayButton); + } +} + +function onClickPlayButton() { + playButton.style.display = 'none'; + + // add video player + videoPlayer.createPlayer(playerDiv, lockMouseCheck); + setupRenderStreaming(); +} + +async function setupRenderStreaming() { + codecPreferences.disabled = true; + + const signaling = useWebSocket ? new WebSocketSignaling() : new Signaling(); + const config = getRTCConfiguration(); + renderstreaming = new RenderStreaming(signaling, config); + renderstreaming.onConnect = onConnect; + renderstreaming.onDisconnect = onDisconnect; + renderstreaming.onTrackEvent = (data) => videoPlayer.addTrack(data.track); + renderstreaming.onGotOffer = setCodecPreferences; + + await renderstreaming.start(); + await renderstreaming.createConnection(); +} + +function onConnect() { + const channel = renderstreaming.createDataChannel("input"); + videoPlayer.setupInput(channel); + multiplayChannel = renderstreaming.createDataChannel("multiplay"); + multiplayChannel.onopen = onOpenMultiplayChannel; + showStatsMessage(); +} + +async function onOpenMultiplayChannel() { + await new Promise(resolve => setTimeout(resolve, 100)); + const num = Math.floor(Math.random() * 100000); + const json = JSON.stringify({ type: ActionType.ChangeLabel, argument: String(num) }); + multiplayChannel.send(json); +} + +async function onDisconnect(connectionId) { + clearStatsMessage(); + messageDiv.style.display = 'block'; + messageDiv.innerText = `Disconnect peer on ${connectionId}.`; + + await renderstreaming.stop(); + renderstreaming = null; + multiplayChannel = null; + videoPlayer.deletePlayer(); + if (supportsSetCodecPreferences) { + codecPreferences.disabled = false; + } + showPlayButton(); +} + +function setCodecPreferences() { + /** @type {RTCRtpCodecCapability[] | null} */ + let selectedCodecs = null; + if (supportsSetCodecPreferences) { + const preferredCodec = codecPreferences.options[codecPreferences.selectedIndex]; + if (preferredCodec.value !== '') { + const [mimeType, sdpFmtpLine] = preferredCodec.value.split(' '); + const { codecs } = RTCRtpSender.getCapabilities('video'); + const selectedCodecIndex = codecs.findIndex(c => c.mimeType === mimeType && c.sdpFmtpLine === sdpFmtpLine); + const selectCodec = codecs[selectedCodecIndex]; + selectedCodecs = [selectCodec]; + } + } + + if (selectedCodecs == null) { + return; + } + const transceivers = renderstreaming.getTransceivers().filter(t => t.receiver.track.kind == "video"); + if (transceivers && transceivers.length > 0) { + transceivers.forEach(t => t.setCodecPreferences(selectedCodecs)); + } +} + +function showCodecSelect() { + if (!supportsSetCodecPreferences) { + messageDiv.style.display = 'block'; + messageDiv.innerHTML = `Current Browser does not support RTCRtpTransceiver.setCodecPreferences.`; + return; + } + + const codecs = RTCRtpSender.getCapabilities('video').codecs; + codecs.forEach(codec => { + if (['video/red', 'video/ulpfec', 'video/rtx'].includes(codec.mimeType)) { + return; + } + const option = document.createElement('option'); + option.value = (codec.mimeType + ' ' + (codec.sdpFmtpLine || '')).trim(); + option.innerText = option.value; + codecPreferences.appendChild(option); + }); + codecPreferences.disabled = false; +} + +/** @type {RTCStatsReport} */ +let lastStats; +/** @type {number} */ +let intervalId; + +function showStatsMessage() { + intervalId = setInterval(async () => { + if (renderstreaming == null) { + return; + } + + const stats = await renderstreaming.getStats(); + if (stats == null) { + return; + } + + const array = createDisplayStringArray(stats, lastStats); + if (array.length) { + messageDiv.style.display = 'block'; + messageDiv.innerHTML = array.join('
    '); + } + lastStats = stats; + }, 1000); +} + +function clearStatsMessage() { + if (intervalId) { + clearInterval(intervalId); + } + lastStats = null; + intervalId = null; + messageDiv.style.display = 'none'; + messageDiv.innerHTML = ''; +} diff --git a/client/public/onebyone/README.md b/client/public/onebyone/README.md new file mode 100644 index 0000000..27e4bfd --- /dev/null +++ b/client/public/onebyone/README.md @@ -0,0 +1,157 @@ +OneByOne 视频通话应用代码结构分析 +1. 项目概述 +OneByOne 是一个基于 WebRTC 和 WebSocket 的一对一视频通话应用,具有现代化的用户界面和丰富的功能。 + +2. 目录结构 + +plainText +onebyone/ +├── index.html # 主HTML文件,包含页面结构 +├── main.js # 主入口文件,初始化应用 +├── renderer.js # UI渲染器,负责将状态映射到DOM +├── store.js # 状态管理,使用Observable模式 +├── models.js # 数据模型定义 +├── websocket.js # WebSocket管理 +├── utils.js # 工具函数 +└── style.css # 样式文件 +3. 核心模块分析 +3.1 数据模型 (models.js) +定义了应用的核心数据结构: + +CallSession: 通话会话,包含通话ID、类型、状态、时长等信息 +LocalUser: 本地用户,包含用户ID、名称、头像、媒体状态等 +RemoteUser: 远端用户,包含用户ID、名称、头像、状态、网络质量等 +MediaState: 媒体状态,包含音频、视频、屏幕共享、录屏、说话状态等 +ChatMessage: 聊天消息,包含消息ID、发送者信息、内容、类型、时间戳等 +3.2 状态管理 (store.js) +使用简单的 Observable 模式实现状态管理: + +核心状态:通话会话、消息列表、侧边栏状态、未读消息数、媒体流等 +状态更新方法: +updateLocalMedia(): 更新本地媒体状态 +updateRemoteMedia(): 更新远端媒体状态 +addMessage(): 添加消息 +toggleSidebar(): 切换侧边栏 +endCall(): 结束通话 +事件通知:通过 notify() 方法通知所有监听器状态变化 +3.3 UI渲染器 (renderer.js) +负责将状态映射到DOM: + +DOM元素缓存:缓存常用DOM元素,提高性能 +渲染方法: +renderHeader(): 渲染头部 +renderCallDuration(): 渲染通话时长 +renderRemoteVideo(): 渲染远端视频 +renderLocalVideo(): 渲染本地视频 +renderControlButtons(): 渲染控制按钮 +renderChatMessages(): 渲染聊天消息 +renderLocalUserStatus(): 渲染本地用户状态 +消息渲染:支持文本消息和图片消息的渲染 +3.4 WebSocket管理 (websocket.js) +管理WebSocket连接和事件: + +连接管理:连接、断开、重连 +消息处理:处理服务器发送的消息 +事件订阅:支持事件订阅和触发 +心跳检测:支持ping/pong心跳机制 +3.5 主入口 (main.js) +初始化应用,连接各个模块: + +应用初始化:初始化渲染器、WebSocket连接、事件绑定 +WebSocket事件绑定:处理服务器事件 +DOM事件绑定:处理用户交互 +功能实现: +麦克风、视频、录屏控制 +聊天消息发送 +图片上传和发送 +通话结束确认 +3.6 样式文件 (style.css) +提供应用的样式: + +基础样式:布局、颜色、字体等 +组件样式:玻璃效果、控制按钮、聊天消息等 +动画效果:消息滑入、音频波形、视频淡入等 +4. 核心功能 +4.1 视频通话 +支持一对一视频通话 +本地视频预览(画中画模式) +远端视频显示 +视频开关控制 +4.2 音频控制 +麦克风开关控制 +说话状态检测 +音频波形动画 +4.3 聊天功能 +文本消息发送 +图片消息发送(支持文件选择和预览) +消息时间戳 +消息类型区分(系统消息、自己的消息、对方的消息) +4.4 录屏功能 +录屏开关控制 +录制状态通知 +4.5 网络状态 +网络质量检测和显示 +连接状态提示 +4.6 其他功能 +端到端加密提示 +通话时长显示 +键盘快捷键(空格键静音,Ctrl+V切换视频) +通知系统 +5. 技术特点 +5.1 模块化设计 +代码按功能模块分离,职责清晰 +使用ES6模块系统,便于维护和扩展 +5.2 状态管理 +使用Observable模式实现简单的状态管理 +状态变化自动触发UI更新 +5.3 响应式设计 +使用Tailwind CSS实现响应式布局 +适配不同屏幕尺寸 +5.4 现代化UI +玻璃拟态效果 +平滑动画 +清晰的视觉层次 +5.5 事件驱动 +使用事件机制处理用户交互和系统通知 +松耦合的组件通信 +6. API接口 +应用定义了以下API接口: + +GET /api/call/:callId - 获取通话信息 +POST /api/call/:callId/join - 加入通话 +POST /api/call/:callId/leave - 离开通话 +POST /api/call/:callId/media - 更新媒体状态 +GET /api/call/:callId/messages - 获取历史消息 +POST /api/call/:callId/message - 发送消息 +7. WebSocket事件 +应用使用WebSocket处理实时通信,支持以下事件: + +connect - 连接建立 +disconnect - 连接断开 +user-joined - 用户加入 +user-left - 用户离开 +media-state-changed - 媒体状态变化 +message-received - 收到消息 +network-quality - 网络质量变化 +call-ended - 通话结束 +8. 代码优化建议 +错误处理:增加更完善的错误处理机制,特别是WebSocket连接和API调用 +性能优化: +减少DOM操作,使用虚拟DOM或批量更新 +优化图片加载和处理 +安全性: +增加输入验证,防止XSS攻击 +实现真正的端到端加密 +可扩展性: +考虑使用更成熟的状态管理库(如Redux) +增加单元测试和集成测试 +用户体验: +增加更多的动画和过渡效果 +优化移动端体验 +增加更多的用户反馈 +9. 总结 +OneByOne 是一个功能完整、界面现代化的一对一视频通话应用,采用了模块化设计和现代化的前端技术。它具有视频通话、音频控制、聊天功能、录屏功能等核心功能,并且支持实时通信和状态管理。 + +应用的代码结构清晰,职责分离明确,使用了Observable模式进行状态管理,WebSocket进行实时通信,Tailwind CSS进行样式设计。这些技术选择使得应用具有良好的可维护性和可扩展性。 + +通过进一步的优化和扩展,OneByOne可以成为一个功能更加强大、用户体验更加出色的视频通话应用。 diff --git a/client/public/onebyone/chatmessage.js b/client/public/onebyone/chatmessage.js new file mode 100644 index 0000000..043d16c --- /dev/null +++ b/client/public/onebyone/chatmessage.js @@ -0,0 +1,248 @@ +/** + * 消息模块 + * 处理聊天消息的发送、接收和显示 + */ +import { showNotification, generateId } from './utils.js'; +import store from './store.js'; +import { mockMessages } from './models.js'; + +const MAX_IMAGE_SIZE = 5 * 1024 * 1024; // 5MB + +// 消息相关的状态管理方法 +let messageState = { + messages: [...mockMessages], + unreadCount: 0, + isSidebarOpen: false +}; + +let listeners = []; + +/** + * 订阅状态变化 + * @param {Function} callback - 回调函数 + * @returns {Function} 取消订阅的函数 + */ +function subscribe(callback) { + listeners.push(callback); + return () => { + listeners = listeners.filter(cb => cb !== callback); + }; +} + +/** + * 通知所有监听器 + * @param {Object} changes - 变化对象 + */ +function notify(changes) { + listeners.forEach(cb => cb(messageState, changes)); +} + +/** + * 添加消息 + * @param {Object} message - 消息对象 + */ +function addMessage (message) { + messageState.messages.push(message); + + // 如果侧边栏关闭且不是自己发的,增加未读 + if (!messageState.isSidebarOpen && !message.isSelf) { + messageState.unreadCount++; + notify({ type: 'SIDEBAR_TOGGLE', unreadCount: messageState.unreadCount }); + } + + notify({ type: 'NEW_MESSAGE', message, unreadCount: messageState.unreadCount }); +} + +/** + * 发送聊天消息 + * @param {Object} message - 消息对象 + * @param {Object} renderstreaming - WebRTC连接管理实例 + */ +function sendChatMessage(message) { + if (store.getRenderStreaming()) { + store.getRenderStreaming().sendMessage({ + type: 'chat-message', + message: message, + }); + } +} + +/** + * 处理接收到的聊天消息 + * @param {Object} data - 消息数据 + */ +function handleChatMessage(data) { + console.log('处理聊天:', data); + addMessage(data); + + const isImage = data.content && data.content.startsWith('data:image/'); + + // 显示通知 + if (!data.isSelf) { + const content = isImage ? '[图片]' : data.content; + showNotification(`${data.senderName}: ${content.substring(0, 20)}${content.length > 20 ? '...' : ''}`); + } +} + +/** + * 切换侧边栏 + * @returns {boolean} 切换后的状态 + */ +function toggleSidebar() { + messageState.isSidebarOpen = !messageState.isSidebarOpen; + if (messageState.isSidebarOpen) { + messageState.unreadCount = 0; + } + notify({ type: 'SIDEBAR_TOGGLE', isOpen: messageState.isSidebarOpen, unreadCount: messageState.unreadCount }); + return messageState.isSidebarOpen; +} + +/** + * 获取消息状态 + * @returns {Object} 消息状态 + */ +function getMessageState() { + return messageState; +} + +/** + * 发送消息 + */ +function sendMessage() { + const chatInput = document.getElementById('chatInput'); + const content = chatInput.value.trim(); + + if (content) { + const state = store.getState(); + const message = { + id: generateId(), + senderId: state.session.localUser.id, + senderName: state.session.localUser.name, + senderAvatar: state.session.localUser.avatar, + content: content, + type: 'text', + timestamp: new Date().toISOString(), + isSelf: true + }; + + addMessage(message); + + const newMessage = { ...message }; + newMessage.isSelf = false; + chatInput.value = ''; + // 发送消息到服务器 + sendChatMessage(newMessage); + + //wsManager.send('chat-message', message); + } +} + +/** + * 处理聊天输入回车 + * @param {KeyboardEvent} event - 键盘事件 + */ +function handleChatSubmit(event) { + if (event.key === 'Enter') { + sendMessage(); + } +} + +/** + * 打开图片选择器 + */ +function openImagePicker() { + document.getElementById('imageInput').click(); +} + +/** + * 处理图片上传 + * @param {Event} event - 事件对象 + */ +function handleImageUpload(event) { + const file = event.target.files[0]; + if (file) { + // 检查文件类型 + if (!file.type.startsWith('image/')) { + showNotification('请选择图片文件', 3000); + return; + } + + // 检查文件大小(限制为5MB) + if (file.size > MAX_IMAGE_SIZE) { + showNotification('图片文件不能超过5MB', 3000); + return; + } + + // 读取图片文件 + const reader = new FileReader(); + reader.onload = function (e) { + const imageUrl = e.target.result; + sendImageMessage(imageUrl, file.name); + }; + reader.readAsDataURL(file); + + // 重置文件输入 + event.target.value = ''; + } +} + +/** + * 发送图片消息 + * @param {string} imageUrl - 图片URL + * @param {string} fileName - 文件名 + */ +function sendImageMessage(imageUrl, fileName) { + const state = store.getState(); + const newMessage = { + id: generateId(), + senderId: state.session.localUser.id, + senderName: state.session.localUser.name, + senderAvatar: state.session.localUser.avatar, + content: imageUrl, + fileName: fileName, + type: 'file', + timestamp: new Date().toISOString(), + isSelf: true + }; + + // 添加消息到本地列表 + addMessage(newMessage); + + // 发送消息到服务器 + const messageToSend = { ...newMessage }; + messageToSend.isSelf = false; + sendChatMessage(messageToSend); +} + +/** + * 绑定消息相关的DOM事件 + */ +function bindMessageEvents() { + // 发送消息 + window.sendMessage = sendMessage; + + // 处理聊天输入回车 + window.handleChatSubmit = handleChatSubmit; + + // 打开图片选择器 + window.openImagePicker = openImagePicker; + + // 处理图片上传 + window.handleImageUpload = handleImageUpload; +} + +// 导出所有函数 +export default { + sendMessage, + handleChatSubmit, + openImagePicker, + handleImageUpload, + sendImageMessage, + bindMessageEvents, + addMessage, + sendChatMessage, + handleChatMessage, + toggleSidebar, + getMessageState, + subscribe +}; diff --git a/client/public/onebyone/code-structure.md b/client/public/onebyone/code-structure.md new file mode 100644 index 0000000..f3731e0 --- /dev/null +++ b/client/public/onebyone/code-structure.md @@ -0,0 +1,458 @@ +# onebyone 模块代码调用结构图 + +> 本文档基于优化后的代码自动生成,反映当前实际架构。 + +--- + +## 3.1 文件依赖关系图 + +```mermaid +graph TD + main[main.js] --> store[store.js] + main --> renderer[renderer.js] + main --> utils[utils.js] + main --> chatmsg[chatmessage.js] + + store --> models[models.js] + store --> utils + store --> chatmsg + store --> signaling[../../module/signaling.js] + store --> rs[../../module/renderstreaming.js] + store --> config[../js/config.js] + + renderer --> utils + renderer --> models + renderer --> chatmsg + renderer -.-> store + + chatmsg --> utils + chatmsg --> store + chatmsg --> models + + connect[connect/connect.js] --> store + connect --> utils + + endcall[endcall/endcall.js] --> utils + + index[index.html] --> main + connectHtml[connect/connect.html] --> connect + endcallHtml[endcall/endcall.html] --> endcall + + style utils fill:#e1f5fe + style models fill:#e1f5fe + style signaling fill:#fff3e0 + style rs fill:#fff3e0 + style config fill:#fff3e0 +``` + +**图例说明** +- 蓝色:内部工具/数据模块 +- 橙色:外部依赖模块(signaling.js、renderstreaming.js、config.js) + +--- + +## 3.2 核心调用链 + +### 通话初始化流程 + +```mermaid +sequenceDiagram + autonumber + participant Browser + participant main as main.js + participant store as store.js + participant render as renderer.js + participant RS as RenderStreaming + participant Sig as Signaling + + Browser->>main: DOMContentLoaded + main->>main: 检查 localStorage.connectionId + alt 无连接ID + main->>Browser: 跳转 connect/connect.html + else 有连接ID + main->>render: new UIRenderer(store) + render->>store: subscribe(render) + main->>store: joinCall(connectionId) + store->>store: init() + store->>store: setupConfig() / getServerConfig() + store->>store: loadUserSettings() + store->>store: getLocalStream() + store->>Browser: getUserMedia(MEDIA_CONSTRAINTS) + Browser-->>store: MediaStream + store->>store: notify(LOCAL_STREAM_OBTAINED) + store->>store: notify(LOCAL_MEDIA_CHANGE x2) + store->>store: emitMediaStateChange() + main->>store: setUp(connectionId) + store->>store: _createSignalingAndRTC() + store->>Sig: new WebSocketSignaling() / new Signaling() + store->>RS: new RenderStreaming(signaling, config) + store->>store: _registerCallbacks() + store->>store: _startConnection() + store->>RS: start() + store->>RS: createConnection(connectionId) + RS-->>store: onConnect(role, participantId) + store->>store: notify(CALL_STATUS_CHANGE, ongoing) + store->>store: sendMessage(user-info) + store->>store: emitMediaStateChange() + main->>main: bindDomEvents() + end +``` + +### 媒体控制流程 + +```mermaid +sequenceDiagram + autonumber + participant User + participant main as main.js + participant store as store.js + participant render as renderer.js + participant RS as RenderStreaming + participant Remote as 远端 + + User->>main: 点击 toggleMute / toggleVideo + main->>store: updateLocalMedia(type, value) + + alt 开启视频 + store->>Browser: getUserMedia(VIDEO_ONLY_CONSTRAINT) + Browser-->>store: newVideoTrack + store->>RS: replaceTrack / addTransceiver + store->>store: notify(LOCAL_STREAM_OBTAINED) + store->>store: notify(LOCAL_MEDIA_CHANGE, video=true) + store->>store: emitMediaStateChange() + else 关闭视频 + store->>store: track.stop() + store->>store: notify(LOCAL_MEDIA_CHANGE, video=false) + store->>store: emitMediaStateChange() + else 切换音频 + store->>store: track.enabled = value + store->>store: notify(LOCAL_MEDIA_CHANGE, audio=value) + store->>store: emitMediaStateChange() + end + + store->>render: notify(USER_LIST_UPDATE) + render->>render: renderUserList() + render->>render: renderControlButtons() + render->>render: renderLocalVideo() + + store->>RS: sendMessage(media-state-changed) + RS->>Remote: WebSocket 信令 + Remote-->>store: onMessage(media-state-changed) + store->>store: updateRemoteMedia() + store->>render: notify(REMOTE_MEDIA_CHANGE) + render->>render: renderRemoteVideo() + render->>render: renderUserList() + render->>render: renderParticipantVideoPlaceholder() +``` + +### 消息发送流程 + +```mermaid +sequenceDiagram + autonumber + participant User + participant chat as chatmessage.js + participant store as store.js + participant render as renderer.js + participant RS as RenderStreaming + participant Remote as 远端 + + User->>chat: 输入消息 / 回车 + chat->>chat: sendMessage() + chat->>chat: addMessage() 本地 + chat->>chat: notify(NEW_MESSAGE) + render->>render: renderMessageState(NEW_MESSAGE) + render->>render: renderChatMessages() + render->>render: renderUnreadCount() + + chat->>store: getRenderStreaming() + chat->>RS: sendMessage(chat-message) + RS->>Remote: WebSocket 信令 + + Remote-->>chat: handleChatMessage(data) + chat->>chat: addMessage() 远端 + chat->>chat: notify(NEW_MESSAGE) + render->>render: renderMessageState(NEW_MESSAGE) + render->>render: renderChatMessages() + render->>render: renderUnreadCount() + + User->>chat: 点击 toggleSidebar() + chat->>chat: notify(SIDEBAR_TOGGLE) + render->>render: renderMessageState(SIDEBAR_TOGGLE) + render->>render: renderSidebar() +``` + +### Participant 管理流程 + +```mermaid +sequenceDiagram + autonumber + participant store as store.js + participant render as renderer.js + participant RS as RenderStreaming + participant Remote as 远端Participant + + Note over RS,Remote: 新 Participant 加入 + RS-->>store: onParticipantJoined(participantId) + store->>store: 初始化 participant 默认信息 + store->>store: notify(PARTICIPANTS_UPDATE) + store->>RS: broadcastParticipantsList() + render->>render: renderUserList() + render->>render: syncParticipantTileNames() + + Note over RS,Remote: Participant 离开 + RS-->>store: onParticipantLeft(participantId) + store->>store: 清理 remoteStreams / participants + store->>store: notify(PARTICIPANT_LEFT) + store->>store: notify(PARTICIPANTS_UPDATE) + store->>RS: broadcastParticipantsList() + render->>render: renderParticipantLeft() + render->>render: renderUserList() + + Note over RS,Remote: 成员列表同步 + RS-->>store: onMessage(participants-sync) + store->>store: 过滤自身条目 -> state.participants + store->>store: notify(PARTICIPANTS_UPDATE) + render->>render: renderUserList() + render->>render: syncParticipantTileNames() +``` + +--- + +## 3.3 状态变化流转图 + +### Store -> Renderer 状态流转 + +```mermaid +graph LR + subgraph StoreNotify[store.js notify 类型] + A[INIT] + B[LOCAL_STREAM_OBTAINED] + C[LOCAL_MEDIA_CHANGE] + D[REMOTE_STREAM_OBTAINED] + E[REMOTE_MEDIA_CHANGE] + F[USER_LIST_UPDATE] + G[PARTICIPANTS_UPDATE] + H[NETWORK_CHANGE] + I[CALL_STATUS_CHANGE] + J[CALL_ENDED] + K[PARTICIPANT_LEFT] + L[DURATION_UPDATE] + end + + subgraph RenderMethod[renderer.js 渲染方法] + RM1[renderRemoteVideo] + RM2[renderLocalVideo] + RM3[renderLocalStream] + RM4[renderRemoteStream] + RM5[renderControlButtons] + RM6[renderUserList] + RM7[renderNetworkStatus] + RM8[renderCallStatus] + RM9[renderCallEnded] + RM10[renderParticipantLeft] + RM11[renderCallDuration] + RM12[renderHeader] + RM13[renderParticipantVideoPlaceholder] + RM14[syncParticipantTileNames] + end + + A --> RM1 & RM2 & RM5 & RM6 & RM12 + B --> RM3 & RM2 + C --> RM5 & RM2 & RM6 + D --> RM4 + E --> RM1 & RM6 & RM13 + F --> RM6 + G --> RM6 & RM14 + H --> RM7 + I --> RM8 + J --> RM9 + K --> RM10 + L --> RM11 +``` + +### chatMessage -> Renderer 状态流转 + +```mermaid +graph LR + subgraph ChatNotify[chatmessage.js notify 类型] + N[NEW_MESSAGE] + S[SIDEBAR_TOGGLE] + end + + subgraph MsgRender[renderer.js 消息渲染] + MR1[renderChatMessages] + MR2[renderUnreadCount] + MR3[renderSidebar] + end + + N --> MR1 & MR2 + S --> MR3 & MR2 +``` + +--- + +## 3.4 各模块导出函数清单 + +### main.js + +| 导出 | 类型 | 说明 | +|------|------|------| +| store | 变量 | 重新导出 store 单例,供外部调试使用 | + +**内部全局函数(绑定到 window):** + +| 函数 | 说明 | +|------|------| +| toggleSidebar | 切换侧边栏显示 | +| toggleMute | 切换麦克风状态 | +| toggleVideo | 切换摄像头状态 | +| toggleLocalVideo | 本地视频悬停控制 | +| toggleRecording | 切换录屏状态 | +| endCall | 显示结束通话确认对话框 | +| cancelEndCall | 取消结束通话 | +| confirmEndCall | 确认结束通话,调用 store.endCall() | +| showCallRequest | 显示通话请求弹窗 | +| rejectCall | 拒绝通话请求 | +| acceptCall | 接受通话请求,初始化通话 | + +### store.js (CallStateManager 类 / store 单例) + +| 方法 | 说明 | +|------|------| +| subscribe(callback) | 订阅状态变化 | +| notify(changes) | 通知所有监听器 | +| init() | 初始化配置、用户设置、本地流 | +| loadUserSettings() | 从 localStorage 加载用户设置 | +| setupConfig() | 获取服务器配置(WebSocket 模式) | +| getLocalStream() | 获取本地摄像头媒体流 | +| updateLocalMedia(mediaType, value) | 更新本地媒体状态(音频/视频/录屏) | +| _createSignalingAndRTC(connectionId) | 创建信令和 RTC 实例 | +| setUp(connectionId) | 设置 WebRTC 连接入口 | +| _registerCallbacks() | 注册所有 WebRTC 回调 | +| _startConnection(connectionId) | 启动连接和检测 | +| hangUp() | 挂断连接,清理资源 | +| sendMessage(type, data) | 通过 RenderStreaming 发送消息 | +| broadcastParticipantsList() | Host 端广播成员列表 | +| setCodecPreferences(participantId) | 设置 H264 编解码器偏好 | +| updateRemoteMedia(mediaState, participantId) | 更新远端媒体状态 | +| updateRemoteUserStatus(status) | 更新远端用户在线状态 | +| updateRemoteUserNetworkQuality(q) | 更新远端网络质量 | +| endCall() | 用户主动结束通话入口 | +| joinCall(connectionId) | 加入通话 | +| createCall() | 创建通话 | +| detectNetworkQuality() | 基于 WebRTC stats 检测网络质量 | +| startActivityDetection(stream, opts) | 启动音频活动检测(VAD) | +| startNetworkQualityDetection() | 启动定时网络质量检测 | +| stopNetworkQualityDetection() | 停止网络质量检测 | +| emitMediaStateChange() | 发送媒体状态变化信令 | +| showStatsMessage() | 显示并定时输出 WebRTC 统计信息 | +| clearStatsMessage() | 清除统计定时器 | +| getState / getLocalUser / getRemoteUser / getConnectionId / getRenderStreaming | Getter 方法 | + +### renderer.js (UIRenderer 类) + +| 方法 | 说明 | +|------|------| +| constructor(stateManager) | 构造函数,订阅 store 和 chatMessage | +| render(state, changes) | 核心渲染分发器,根据 changes.type 路由 | +| renderMessageState(msgState, changes) | 消息状态渲染分发器 | +| renderCallStatus(status) | 渲染通话状态(连接中覆盖层) | +| renderHeader(session) | 渲染头部信息 | +| renderHeaderTitle() | 渲染标题(含 connectionId) | +| renderCallDuration(duration) | 渲染通话时长 | +| renderRemoteVideo(remoteUser) | 渲染远端视频和占位符 | +| renderHeaderNetworkStatus(q) | 渲染头部网络质量文本 | +| renderLocalVideo(localUser, stream) | 渲染本地视频和占位符 | +| renderLocalStream(stream) | 将本地流绑定到 video 元素 | +| renderRemoteStream(stream, id, isHost) | 远端流渲染分发 | +| renderParticipantStream(stream, id) | Host 端多 participant 视频网格渲染 | +| renderParticipantVideoPlaceholder(id, show) | 精准更新 participant tile 占位背景 | +| syncParticipantTileNames(participants) | 同步所有 tile 名称标签 | +| updateParticipantTileName(id, name) | 更新指定 tile 名称 | +| renderSingleRemoteStream(stream) | Participant 端单路远端视频渲染 | +| renderLocalUserStatus(localUser) | 渲染本地用户媒体状态文本 | +| renderUserList(local, remote, participants) | 渲染侧边栏用户列表(支持多 participant) | +| createUserEntry(options) | 创建通用用户条目 DOM | +| getVideoResolution(track) | 获取视频轨道分辨率 | +| adjustVideoSize(video, resolution) | 调整视频元素尺寸 | +| renderControlButtons(mediaState) | 渲染底部控制按钮状态 | +| renderChatMessages(messages) | 渲染聊天消息列表 | +| createMessageElement(message) | 创建单条消息 DOM | +| renderUnreadCount(count) | 渲染未读消息角标 | +| renderSidebar(isOpen) | 渲染侧边栏显隐 | +| renderNetworkStatus(quality) | 渲染网络状态提示 | +| updateHeaderNetworkIndicator(q) | 更新头部网络指示器颜色 | +| renderCallEnded() | 渲染通话结束,跳转 endcall 页面 | +| renderParticipantLeft(id) | 清理 participant tile | +| getStatusText / getNetworkQualityText | 状态/质量文本映射 | +| destroy() | 销毁,取消订阅 | + +### chatmessage.js + +| 导出函数 | 说明 | +|----------|------| +| subscribe(callback) | 订阅消息状态变化 | +| addMessage(message) | 添加消息到列表 | +| sendChatMessage(message) | 通过 RenderStreaming 发送聊天消息 | +| handleChatMessage(data) | 处理接收到的聊天消息 | +| toggleSidebar() | 切换侧边栏,重置未读数 | +| getMessageState() | 获取当前消息状态 | +| sendMessage() | 用户发送消息入口(文本) | +| handleChatSubmit(event) | 回车发送处理 | +| openImagePicker() | 打开图片选择器 | +| handleImageUpload(event) | 处理图片上传(限制 5MB) | +| sendImageMessage(url, fileName) | 发送图片消息 | +| bindMessageEvents() | 绑定消息相关 DOM 事件到 window | + +### utils.js + +| 导出函数 | 说明 | +|----------|------| +| formatTime(seconds) | 格式化为 MM:SS | +| formatTimestamp(timestamp) | 格式化为 HH:MM | +| generateId() | 生成随机唯一 ID | +| showNotification(message, duration) | 显示顶部通知 | +| toggleElement(element, show) | 切换元素显隐(hidden 类) | +| toggleButtonState(button, active) | 切换按钮图标状态 | + +### models.js + +| 导出 | 类型 | 说明 | +|------|------|------| +| mockCallSession | 常量对象 | 默认通话会话数据结构(含 localUser / remoteUser) | +| mockMessages | 常量数组 | 默认聊天消息数组(含系统消息) | + +### connect/connect.js + +| 全局函数 | 说明 | +|----------|------| +| joinCall() | 读取输入框 connectionId,保存并跳转 | +| createCall() | 生成随机 connectionId,保存并跳转 | +| getAllConnectionIds() | 从服务器获取所有连接 ID 列表 | +| displayConnectionIds(ids) | 渲染连接 ID 列表到 DOM | +| selectConnectionId(id) | 选择指定 ID 填充输入框 | +| saveSettings() | 保存用户设置到 localStorage | +| handleAvatarUpload(event) | 上传头像(限制 2MB) | +| copyUserId() | 复制用户 ID 到剪贴板 | +| toggleSettingsMenu() | 切换设置菜单显隐 | + +### endcall/endcall.js + +| 全局函数 | 说明 | +|----------|------| +| reconnectCall() | 重新连接,跳转 index.html | +| leaveCall() | 清除 connectionId,跳转 connect.html | + +--- + +## 附录:页面跳转关系 + +```mermaid +graph LR + connect[connect/connect.html] -->|joinCall / createCall| index[index.html] + index -->|endCall| endcall[endcall/endcall.html] + endcall -->|reconnectCall| index + endcall -->|leaveCall| connect + index -->|无 connectionId| connect +``` diff --git a/client/public/onebyone/connect/connect.html b/client/public/onebyone/connect/connect.html new file mode 100644 index 0000000..7ef9a18 --- /dev/null +++ b/client/public/onebyone/connect/connect.html @@ -0,0 +1,114 @@ + + + + + + VideoCall - 连接界面 + + + + + + +
    + + + + +
    + +
    +
    +
    + +
    +

    VideoCall

    +

    一对一视频通话

    + +
    +
    + + +
    +

    + 连接ID是用于建立点对点通话的唯一标识,由发起方生成并分享给接收方。 +

    +
    + +
    + + +
    + + + + + + +
    +
    + + +
    + + 通知内容 +
    + + + + + diff --git a/client/public/onebyone/connect/connect.js b/client/public/onebyone/connect/connect.js new file mode 100644 index 0000000..ccd4e35 --- /dev/null +++ b/client/public/onebyone/connect/connect.js @@ -0,0 +1,323 @@ +/** + * 连接界面逻辑 + * 处理初始连接、创建通话和加入通话的功能 + */ + +import { showNotification } from '../utils.js'; + +const MAX_AVATAR_SIZE = 2 * 1024 * 1024; // 2MB + +// 加入通话 +function joinCall() { + const connectionId = document.getElementById('connectionIdInput').value.trim(); + if (connectionId) { + showNotification(`正在加入通话 (${connectionId})`); + + // 保存连接ID到本地存储 + localStorage.setItem('connectionId', connectionId); + + // 跳转到通话界面 + window.location.href = '../index.html'; + } else { + showNotification('请输入连接ID', 'error'); + } +} + +// 创建通话 +function createCall() { + showNotification('正在创建通话...'); + + // 生成随机连接ID + const connectionId = 'conn_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); + + // 保存连接ID到本地存储 + localStorage.setItem('connectionId', connectionId); + + // 跳转到通话界面 + window.location.href = '../index.html'; +} + + +// 获取所有连接ID +async function getAllConnectionIds() { + showNotification('正在获取连接ID列表...'); + try { + const response = await fetch('/signaling/connection-ids'); + if (!response.ok) { + throw new Error('Failed to fetch connection IDs'); + } + const data = await response.json(); + displayConnectionIds(data.connectionIds); + } catch (error) { + console.error('Error fetching connection IDs:', error); + showNotification('获取连接ID失败', 'error'); + } +} + +// 显示连接ID列表 +function displayConnectionIds(connectionIds) { + const idsContainer = document.getElementById('idsContainer'); + const connectionIdsList = document.getElementById('connectionIdsList'); + + if (idsContainer) { + // 清空容器 + idsContainer.innerHTML = ''; + + if (connectionIds.length === 0) { + idsContainer.innerHTML = '

    暂无可用的连接ID

    '; + } else { + // 为每个连接ID创建一个元素 + connectionIds.forEach(id => { + const idElement = document.createElement('div'); + idElement.className = 'flex items-center justify-between p-2 bg-white/5 rounded-lg hover:bg-white/10 cursor-pointer transition-colors'; + idElement.innerHTML = ` + ${id} + + `; + idsContainer.appendChild(idElement); + }); + } + + // 显示连接ID列表 + if (connectionIdsList) { + connectionIdsList.classList.remove('hidden'); + } + + showNotification(`找到 ${connectionIds.length} 个连接ID`); + } +} + +// 选择连接ID +function selectConnectionId(id) { + const connectionIdInput = document.getElementById('connectionIdInput'); + if (connectionIdInput) { + connectionIdInput.value = id; + showNotification(`已选择连接ID: ${id}`); + } +} + +// 绑定事件监听器 +function bindEvents() { + // 连接按钮 + const connectBtn = document.getElementById('connectBtn'); + if (connectBtn) { + connectBtn.addEventListener('click', joinCall); + } + + // 创建通话按钮 + const createCallBtn = document.getElementById('createCallBtn'); + if (createCallBtn) { + createCallBtn.addEventListener('click', createCall); + } + + // 浏览全部ID按钮 + const browseIdsBtn = document.getElementById('browseIdsBtn'); + if (browseIdsBtn) { + browseIdsBtn.addEventListener('click', getAllConnectionIds); + } + + // 输入框回车事件 + const connectionIdInput = document.getElementById('connectionIdInput'); + if (connectionIdInput) { + connectionIdInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + joinCall(); + } + }); + } +} + +// 生成8位的用户ID +function generateUserId() { + // 生成8位随机字符串,包含字母和数字 + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = 'user_'; + for (let i = 0; i < 8; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; +} + +// 加载用户设置 +function loadUserSettings() { + const defaultAvatar = '/images/p1.png'; + const userSettings = localStorage.getItem('userSettings'); + if (userSettings) { + try { + const settings = JSON.parse(userSettings); + + // 设置用户ID + if (settings.userId) { + document.getElementById('userIdInput').value = settings.userId; + } + + // 设置昵称 + if (settings.name) { + document.getElementById('nicknameInput').value = settings.name; + document.getElementById('userName').textContent = settings.name; + } + + // 设置头像 + const avatar = settings.avatar || defaultAvatar; + document.getElementById('userAvatar').src = avatar; + document.getElementById('avatarPreview').src = avatar; + } catch (error) { + console.error('Error loading user settings:', error); + // 加载失败时使用默认头像 + const defaultAvatar = '/images/p1.png'; + document.getElementById('userAvatar').src = defaultAvatar; + document.getElementById('avatarPreview').src = defaultAvatar; + } + } else { + // 生成新的用户ID + const newUserId = generateUserId(); + document.getElementById('userIdInput').value = newUserId; + + // 使用默认头像 + const defaultAvatar = '/images/p1.png'; + document.getElementById('userAvatar').src = defaultAvatar; + document.getElementById('avatarPreview').src = defaultAvatar; + + // 保存默认设置 + saveSettings(); + } +} + +// 保存用户设置 +function saveSettings() { + const defaultAvatar = '/images/p1.png'; + const settings = { + userId: document.getElementById('userIdInput').value, + name: document.getElementById('nicknameInput').value || '我', + avatar: document.getElementById('avatarPreview').src || defaultAvatar + }; + + localStorage.setItem('userSettings', JSON.stringify(settings)); + + // 更新界面显示 + document.getElementById('userName').textContent = settings.name; + document.getElementById('userAvatar').src = settings.avatar; + + showNotification('设置已保存', 'success'); +} + +// 处理头像上传 +function handleAvatarUpload(event) { + const file = event.target.files[0]; + if (file) { + // 检查文件类型 + if (!file.type.startsWith('image/')) { + showNotification('请选择图片文件', 'error'); + return; + } + + // 检查文件大小 + if (file.size > MAX_AVATAR_SIZE) { // 2MB限制 + showNotification('图片大小不能超过2MB', 'error'); + return; + } + + // 创建FormData对象 + const formData = new FormData(); + formData.append('avatar', file); + formData.append('userId', document.getElementById('userIdInput').value); + + // 显示上传中通知 + showNotification('正在上传头像...'); + + // 上传头像到服务器 + fetch('/api/upload/avatar', { + method: 'POST', + body: formData + }) + .then(response => { + if (!response.ok) { + throw new Error('上传失败'); + } + return response.json(); + }) + .then(data => { + if (data.success && data.avatarUrl) { + // 更新预览和本地存储 + const avatarUrl = data.avatarUrl; + document.getElementById('avatarPreview').src = avatarUrl; + document.getElementById('userAvatar').src = avatarUrl; + + // 保存设置 + saveSettings(); + + showNotification('头像上传成功', 'success'); + } else { + throw new Error('上传失败:' + (data.message || '未知错误')); + } + }) + .catch(error => { + console.error('Error uploading avatar:', error); + showNotification('头像上传失败,请重试', 'error'); + + // 上传失败时,使用默认头像 + const defaultAvatar = '/images/p1.png'; + document.getElementById('avatarPreview').src = defaultAvatar; + }); + } +} + +// 复制用户ID到剪贴板 +function copyUserId() { + const userIdInput = document.getElementById('userIdInput'); + userIdInput.select(); + document.execCommand('copy'); + showNotification('用户ID已复制到剪贴板', 'success'); +} + +// 切换设置菜单 +function toggleSettingsMenu() { + const settingsMenu = document.getElementById('settingsMenu'); + settingsMenu.classList.toggle('hidden'); +} + +// 点击外部关闭设置菜单 +document.addEventListener('click', function(event) { + const settingsMenu = document.getElementById('settingsMenu'); + const userSettingsBtn = document.getElementById('userSettingsBtn'); + + if (!settingsMenu.contains(event.target) && !userSettingsBtn.contains(event.target)) { + settingsMenu.classList.add('hidden'); + } +}); + +// 绑定用户设置相关事件 +function bindUserSettingsEvents() { + // 设置按钮点击事件 + const userSettingsBtn = document.getElementById('userSettingsBtn'); + if (userSettingsBtn) { + userSettingsBtn.addEventListener('click', toggleSettingsMenu); + } +} + +// 页面加载完成后初始化 +window.addEventListener('DOMContentLoaded', () => { + bindEvents(); + bindUserSettingsEvents(); + + // 检查本地存储中是否有连接ID + const savedConnectionId = localStorage.getItem('connectionId'); + if (savedConnectionId) { + const connectionIdInput = document.getElementById('connectionIdInput'); + if (connectionIdInput) { + connectionIdInput.value = savedConnectionId; + } + } + + // 加载用户设置 + loadUserSettings(); +}); + +// 导出全局函数 +window.joinCall = joinCall; +window.createCall = createCall; +window.selectConnectionId = selectConnectionId; +window.saveSettings = saveSettings; +window.handleAvatarUpload = handleAvatarUpload; +window.copyUserId = copyUserId; +window.toggleSettingsMenu = toggleSettingsMenu; diff --git a/client/public/onebyone/css/style.css b/client/public/onebyone/css/style.css new file mode 100644 index 0000000..7277e68 --- /dev/null +++ b/client/public/onebyone/css/style.css @@ -0,0 +1,255 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); + +body { + font-family: 'Inter', sans-serif; + background: #0f172a; + overflow: hidden; +} + +.bg-grid { + background-image: + linear-gradient(rgba(99, 102, 241, 0.1) 1px, transparent 1px), + linear-gradient(90deg, rgba(99, 102, 241, 0.1) 1px, transparent 1px); + background-size: 40px 40px; + +} + +@keyframes gridMove { + 0% { transform: translate(0, 0); } + 100% { transform: translate(20px, 20px); } +} + +.glass { + background: rgba(30, 41, 59, 0.7); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.glass-strong { + background: rgba(15, 23, 42, 0.9); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); +} + +.control-btn { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.control-btn:hover { + transform: translateY(-2px); + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3); +} + +.end-call-pulse { + animation: pulse-red 2s infinite; +} + +@keyframes pulse-red { + 0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7); } + 50% { box-shadow: 0 0 0 15px rgba(239, 68, 68, 0); } +} + +.chat-bubble { + animation: messageSlide 0.3s ease-out; + margin-bottom: 12px; +} + +@keyframes messageSlide { + from { opacity: 0; transform: translateX(-10px); } + to { opacity: 1; transform: translateX(0); } +} + +/* 消息样式 */ +.message-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 4px; +} + +.message-header img { + width: 24px; + height: 24px; + border-radius: 50%; + object-fit: cover; +} + +.message-sender { + font-size: 12px; + font-weight: 500; +} + +.message-time { + font-size: 12px; + color: #94a3b8; +} + +.message-content { + padding: 8px 16px; + border-radius: 8px; + font-size: 14px; + max-width: 70%; +} + +/* 系统消息 */ +.message-system .message-sender { + color: #60a5fa; +} + +.message-system .message-content { + background-color: rgba(30, 64, 175, 0.3); + color: #ffffff; +} + +/* 对方消息 */ +.message-other .message-sender { + color: #a5b4fc; +} + +.message-other .message-content { + background-color: #1e293b; + color: #ffffff; + border-top-left-radius: 0; +} + +/* 自己的消息 */ +.message-self { + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.message-self .message-header { + flex-direction: row-reverse; +} + +.message-self .message-sender { + color: #4ade80; +} + +.message-self .message-content { + background-color: #4f46e5; + color: #ffffff; + border-top-right-radius: 0; +} + +/* 图片消息样式 */ +.message-image-container { + display: flex; + flex-direction: column; + gap: 8px; +} + +.message-image { + max-width: 200px; + max-height: 200px; + border-radius: 8px; + object-fit: cover; +} + +.message-image-name { + font-size: 12px; + color: rgba(255, 255, 255, 0.7); + text-align: center; +} + +.message-text { + word-wrap: break-word; +} + +.custom-scrollbar::-webkit-scrollbar { + width: 6px; +} +.custom-scrollbar::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); +} +.custom-scrollbar::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + border-radius: 3px; +} + +.audio-wave { + display: flex; + align-items: center; + gap: 3px; + height: 20px; +} + +.audio-wave span { + width: 3px; + background: #10b981; + border-radius: 2px; + animation: wave 1s ease-in-out infinite; +} + +.audio-wave span:nth-child(1) { animation-delay: 0s; height: 40%; } +.audio-wave span:nth-child(2) { animation-delay: 0.1s; height: 70%; } +.audio-wave span:nth-child(3) { animation-delay: 0.2s; height: 100%; } +.audio-wave span:nth-child(4) { animation-delay: 0.3s; height: 60%; } +.audio-wave span:nth-child(5) { animation-delay: 0.4s; height: 30%; } + +@keyframes wave { + 0%, 100% { transform: scaleY(0.5); } + 50% { transform: scaleY(1); } +} + +.video-fade-in { + animation: videoFadeIn 0.5s ease-out; +} + +@keyframes videoFadeIn { + from { opacity: 0; transform: scale(1.05); } + to { opacity: 1; transform: scale(1); } +} + +/* 数据绑定标记 - 开发调试时显示 +[data-field]::after { + content: attr(data-field); + position: absolute; + top: -18px; + right: 0; + background: #f59e0b; + color: #000; + font-size: 9px; + padding: 1px 4px; + border-radius: 3px; + opacity: 0; + transition: opacity 0.2s; + pointer-events: none; + z-index: 1000; + white-space: nowrap; +} +[data-field]:hover::after { opacity: 1; } +[data-field] { position: relative; }*/ + +/* 分辨率选项样式 */ +.resolution-option { + color: rgba(255, 255, 255, 0.8); + cursor: pointer; + transition: all 0.2s; +} +.resolution-option:hover { + color: white; +} +.resolution-option.active { + background: rgba(99, 102, 241, 0.3); + color: white; +} +.resolution-option.active::before { + content: '\f00c'; + font-family: 'Font Awesome 6 Free'; + font-weight: 900; + font-size: 10px; + margin-right: 6px; + color: #818cf8; +} + +/* 更多选项菜单动画 */ +#moreOptionsMenu { + animation: menuFadeIn 0.15s ease-out; +} +@keyframes menuFadeIn { + from { opacity: 0; transform: translateX(-50%) translateY(8px); } + to { opacity: 1; transform: translateX(-50%) translateY(0); } +} diff --git a/client/public/onebyone/endcall/endcall.html b/client/public/onebyone/endcall/endcall.html new file mode 100644 index 0000000..21710db --- /dev/null +++ b/client/public/onebyone/endcall/endcall.html @@ -0,0 +1,50 @@ + + + + + + VideoCall - 通话结束 + + + + + + +
    +
    +
    + +
    +

    通话已结束

    +

    连接已断开,请检查网络连接后重试

    +
    + + +
    +
    +

    连接ID: --

    +

    断开时间: --

    +
    +
    +
    + + +
    + + 通知内容 +
    + + + + + diff --git a/client/public/onebyone/endcall/endcall.js b/client/public/onebyone/endcall/endcall.js new file mode 100644 index 0000000..3cf0a70 --- /dev/null +++ b/client/public/onebyone/endcall/endcall.js @@ -0,0 +1,59 @@ +/** + * 结束通话界面逻辑 + * 处理通话结束后的操作,如重新连接或返回连接界面 + */ + +import { showNotification } from '../utils.js'; + +// 重新连接 +function reconnectCall() { + showNotification('正在重新连接...'); + + // 跳转到通话界面 + window.location.href = '../index.html'; +} + +// 离开 +function leaveCall() { + // 清除本地存储中的连接ID + localStorage.removeItem('connectionId'); + + // 跳转到连接界面 + window.location.href = '../connect/connect.html'; +} + +// 绑定事件监听器 +function bindEvents() { + // 重新连接按钮 + const reconnectBtn = document.getElementById('reconnectBtn'); + if (reconnectBtn) { + reconnectBtn.addEventListener('click', reconnectCall); + } + + // 离开按钮 + const leaveBtn = document.getElementById('leaveBtn'); + if (leaveBtn) { + leaveBtn.addEventListener('click', leaveCall); + } +} + +// 页面加载完成后初始化 +window.addEventListener('DOMContentLoaded', () => { + bindEvents(); + + // 更新断开连接信息 + const disconnectConnectionId = document.getElementById('disconnectConnectionId'); + const disconnectTime = document.getElementById('disconnectTime'); + + if (disconnectConnectionId) { + disconnectConnectionId.textContent = localStorage.getItem('connectionId') || '--'; + } + + if (disconnectTime) { + disconnectTime.textContent = new Date().toLocaleString(); + } +}); + +// 导出全局函数 +window.reconnectCall = reconnectCall; +window.leaveCall = leaveCall; diff --git a/client/public/onebyone/index.html b/client/public/onebyone/index.html new file mode 100644 index 0000000..e92815b --- /dev/null +++ b/client/public/onebyone/index.html @@ -0,0 +1,614 @@ + + + + + + + VideoCall - 一对一视频通话 + + + + + + + + + + + +
    +
    +
    + +
    +
    + +

    + 与 Sarah 的通话 +

    +
    + + + + 优秀 + + + 00:00 +
    +
    +
    + +
    + + + + +
    +
    + +
    + + +
    + + + + + +
    + + + + + +
    +
    +
    + +
    +

    等待对方连接...

    +

    请确保对方已加入通话

    +
    +
    + + + + + + + + + + + + +
    + + +
    + + + + + + + +
    + + + +
    + + +
    + +
    +
    + +
    + + + + +
    + + +
    + + + + + +
    + + + + + + + + + + + + + + +
    + + + +
    + + + + +
    + + +
    + +
    + +
    + + +
    + + 通知内容 +
    + + + + + + + + + + + + + + diff --git a/client/public/onebyone/knowledge-graph.md b/client/public/onebyone/knowledge-graph.md new file mode 100644 index 0000000..972e808 --- /dev/null +++ b/client/public/onebyone/knowledge-graph.md @@ -0,0 +1,365 @@ +# OneByOne 知识图谱 + +## 1. 模块依赖关系图 + +```mermaid +graph TB + subgraph Page["📄 页面层"] + INDEX[index.html
    主通话页面] + CONN[connect/connect.html
    连接页面] + END[endcall/endcall.html
    结束页面] + end + + subgraph Core["⚙️ 核心模块"] + MAIN[main.js
    应用入口] + STORE[store.js
    状态管理] + RENDER[renderer.js
    UI渲染器] + CHAT[chatmessage.js
    消息模块] + end + + subgraph Base["📦 数据与工具"] + MODELS[models.js
    数据模型] + UTILS[utils.js
    工具函数] + end + + subgraph External["🔗 外部依赖"] + SIGNALING[signaling.js
    信令管理] + RENDERSTR[renderstreaming.js
    WebRTC管理] + CONFIG[config.js
    配置管理] + end + + INDEX --> MAIN + CONN --> CONN_JS[connect.js] + END --> END_JS[endcall.js] + + MAIN --> STORE + MAIN --> RENDER + MAIN --> UTILS + MAIN --> CHAT + + STORE --> MODELS + STORE --> SIGNALING + STORE --> RENDERSTR + STORE --> CONFIG + STORE --> UTILS + STORE --> CHAT + + RENDER --> UTILS + RENDER --> MODELS + RENDER --> CHAT + + CHAT --> UTILS + CHAT --> STORE + CHAT --> MODELS + + CONN_JS --> STORE + + style STORE fill:#f9f,stroke:#333,stroke-width:2px + style RENDER fill:#bbf,stroke:#333,stroke-width:2px + style CHAT fill:#bfb,stroke:#333,stroke-width:2px +``` + +## 2. 数据模型关系图 + +```mermaid +graph TB + subgraph Models["数据模型结构"] + SESSION[CallSession
    通话会话] + LOCAL[LocalUser
    本地用户] + REMOTE[RemoteUser
    远端用户] + MEDIA[MediaState
    媒体状态] + MSG[ChatMessage
    聊天消息] + end + + SESSION --> LOCAL + SESSION --> REMOTE + LOCAL --> MEDIA + REMOTE --> MEDIA + + style SESSION fill:#f96,stroke:#333,stroke-width:2px + style MEDIA fill:#9f6,stroke:#333,stroke-width:2px +``` + +### 模型属性说明 + +| 模型 | 属性 | +|-----|------| +| **CallSession** | id, type, status, startTime, duration, isEncrypted | +| **LocalUser** | id, name, avatar, isHost, mediaState | +| **RemoteUser** | id, name, avatar, status, networkQuality, mediaState | +| **MediaState** | audio, video, screenShare, recording, isSpeaking | +| **ChatMessage** | id, senderId, senderName, content, type, timestamp, isSelf | + +## 3. 状态更新时序图 + +```mermaid +sequenceDiagram + autonumber + participant USER as 👤 用户 + participant DOM as 🖱️ DOM事件 + participant MAIN as ⚙️ main.js + participant STORE as 📦 store.js + participant RENDER as 🎨 renderer.js + participant UI as 🖥️ UI界面 + + USER->>DOM: 点击麦克风按钮 + DOM->>MAIN: toggleMute() + MAIN->>STORE: updateLocalMedia('audio', false) + STORE->>STORE: 更新 mediaState.audio + STORE->>STORE: notify({type: 'LOCAL_MEDIA_CHANGE'}) + STORE->>RENDER: 回调 render(state, changes) + RENDER->>RENDER: renderControlButtons(mediaState) + RENDER->>RENDER: renderLocalUserStatus(localUser) + RENDER->>UI: 更新DOM + UI->>USER: 显示更新后的界面 +``` + +## 4. WebRTC 连接建立时序图 + +```mermaid +sequenceDiagram + autonumber + participant USER as 👤 用户 + participant MAIN as ⚙️ main.js + participant STORE as 📦 store.js + participant SIGNAL as 📡 signaling.js + participant RTC as 🔗 renderstreaming.js + participant PEER as 👤 远端对等端 + + USER->>MAIN: 访问页面 + MAIN->>STORE: joinCall(connectionId) + STORE->>STORE: init() + STORE->>STORE: getLocalStream()
    获取本地摄像头 + MAIN->>STORE: setUp(connectionId) + STORE->>SIGNAL: 创建信令实例 + STORE->>RTC: new RenderStreaming
    (signaling, config) + RTC->>SIGNAL: start() + RTC->>SIGNAL: createConnection(connectionId) + + loop WebSocket信令交换 + PEER->>SIGNAL: 交换SDP/ICE + SIGNAL->>RTC: 信令数据 + end + + RTC->>STORE: onConnect 回调 + STORE->>STORE: 状态更新为 ongoing + PEER->>RTC: 接收媒体流 + RTC->>STORE: onTrackEvent 回调 + STORE->>STORE: notify(REMOTE_STREAM_OBTAINED) + STORE->>RENDER: 触发渲染更新 + RENDER->>UI: 显示远端视频 +``` + +## 5. 消息发送流程 + +```mermaid +sequenceDiagram + autonumber + participant USER as 👤 用户 + participant DOM as 🖱️ DOM + participant CHAT as 💬 chatmessage.js + participant STORE as 📦 store.js + participant RTC as 🔗 renderstreaming.js + participant PEER as 👤 远端用户 + + USER->>DOM: 输入消息并回车 + DOM->>CHAT: handleChatSubmit(event) + CHAT->>CHAT: sendMessage() + CHAT->>CHAT: addMessage(message)
    添加到本地列表 + CHAT->>STORE: getRenderStreaming() + STORE-->>CHAT: 返回实例 + CHAT->>RTC: sendMessage()
    发送chat-message + RTC->>PEER: 通过WebRTC数据通道发送 + + PEER->>RTC: 收到消息 + RTC->>STORE: onMessage 回调 + STORE->>CHAT: handleChatMessage(data) + CHAT->>CHAT: addMessage()
    添加到消息列表 + CHAT->>RENDER: 更新聊天UI +``` + +## 6. 页面流转状态图 + +```mermaid +stateDiagram-v2 + [*] --> 连接页面: 首次访问/无connectionId + + 连接页面: connect.html + 连接页面: - 输入连接ID + 连接页面: - 创建新通话 + 连接页面: - 用户设置 + + 通话页面: index.html + 通话页面: - 视频通话 + 通话页面: - 聊天功能 + 通话页面: - 媒体控制 + + 结束页面: endcall.html + 结束页面: - 显示断开原因 + 结束页面: - 重新连接选项 + + 连接页面 --> 通话页面: 加入/创建通话 + 通话页面 --> 结束页面: 通话结束/断开 + 通话页面 --> 连接页面: 无有效connectionId + 结束页面 --> 通话页面: 重新连接 + 结束页面 --> 连接页面: 返回 +``` + +## 7. 通话状态机 + +```mermaid +stateDiagram-v2 + [*] --> idle: 初始化 + + idle: 空闲状态 + idle --> connecting: joinCall()
    加入通话 + idle --> connecting: createCall()
    创建通话 + + connecting: 连接中 + connecting --> ongoing: onConnect
    连接成功 + connecting --> failed: 连接失败 + + ongoing: 通话中 + ongoing --> ended: hangUp()
    挂断 + ongoing --> ended: endCall()
    结束通话 + + failed: 连接失败 + failed --> connecting: 重试连接 + failed --> ended: 放弃连接 + + ended: 已结束 + ended --> [*]: 清理资源 +``` + +## 8. 组件层次结构 + +```mermaid +graph TD + subgraph index["index.html 组件结构"] + HEADER["🧭 Header 顶部栏"] + MAIN["📺 Main 主内容区"] + FOOTER["🎛️ Footer 底部控制栏"] + end + + subgraph header["Header 内容"] + TITLE["标题: 与xxx的通话"] + NETWORK["网络质量指示"] + DURATION["通话时长"] + ENCRYPT["端到端加密标识"] + end + + subgraph main["Main 内容"] + VIDEO_AREA["VideoArea 视频区"] + SIDEBAR["Sidebar 侧边栏"] + end + + subgraph video["VideoArea"] + REMOTE["RemoteVideo 远端视频"] + LOCAL["LocalVideo 本地视频画中画"] + end + + subgraph sidebar["Sidebar"] + USER_LIST["UserList 用户列表"] + CHAT_MSG["ChatMessages 聊天消息"] + CHAT_INPUT["ChatInput 消息输入"] + end + + subgraph footer["Footer 控制栏"] + CTRL_MIC["🎤 麦克风"] + CTRL_VIDEO["📹 视频"] + CTRL_RECORD["🔴 录制"] + CTRL_END["📞 结束通话"] + CTRL_CHAT["💬 聊天"] + end + + HEADER --> TITLE + HEADER --> NETWORK + HEADER --> DURATION + HEADER --> ENCRYPT + + MAIN --> VIDEO_AREA + MAIN --> SIDEBAR + + VIDEO_AREA --> REMOTE + VIDEO_AREA --> LOCAL + + SIDEBAR --> USER_LIST + SIDEBAR --> CHAT_MSG + SIDEBAR --> CHAT_INPUT + + FOOTER --> CTRL_MIC + FOOTER --> CTRL_VIDEO + FOOTER --> CTRL_RECORD + FOOTER --> CTRL_END + FOOTER --> CTRL_CHAT + + style CTRL_END fill:#f66,stroke:#333,stroke-width:2px + style ENCRYPT fill:#9f6,stroke:#333,stroke-width:2px +``` + +## 9. 文件引用汇总 + +```mermaid +graph LR + subgraph Import["导入关系"] + direction TB + M[main.js] + S[store.js] + R[renderer.js] + C[chatmessage.js] + CONN[connect.js] + + M --> S + M --> R + M --> C + S --> C + R --> C + CONN --> S + end + + subgraph Export["导出内容"] + direction TB + ST[store.js
    export default store] + RE[renderer.js
    export default UIRenderer] + UT[utils.js
    export { formatTime, showNotification, ... }] + MO[models.js
    export { mockCallSession, mockMessages }] + CH[chatmessage.js
    export { sendMessage, toggleSidebar, ... }] + end +``` + +## 10. API 接口调用图 + +```mermaid +graph LR + subgraph Client["客户端"] + CONN_JS[connect.js] + end + + subgraph Server["服务器"] + API_CALL[/api/call/:callId] + API_JOIN[/api/call/:callId/join] + API_LEAVE[/api/call/:callId/leave] + API_MEDIA[/api/call/:callId/media] + API_MSG[/api/call/:callId/messages] + API_SEND[/api/call/:callId/message] + API_CONN[/signaling/connection-ids] + API_UPLOAD[/api/upload/avatar] + end + + CONN_JS -.-> API_CONN + CONN_JS -.-> API_UPLOAD + + style API_CALL fill:#bbf,stroke:#333 + style API_JOIN fill:#bbf,stroke:#333 + style API_LEAVE fill:#bbf,stroke:#333 + style API_MEDIA fill:#bbf,stroke:#333 + style API_MSG fill:#bbf,stroke:#333 + style API_SEND fill:#bbf,stroke:#333 + style API_CONN fill:#fbf,stroke:#333 + style API_UPLOAD fill:#fbf,stroke:#333 +``` + +--- + +*文档生成时间: 2026-04-11* +*适用于 OneByOne 视频通话应用* diff --git a/client/public/onebyone/main.js b/client/public/onebyone/main.js new file mode 100644 index 0000000..3138180 --- /dev/null +++ b/client/public/onebyone/main.js @@ -0,0 +1,215 @@ +/** + * 主入口文件 + * 初始化应用,连接各个模块 + */ +import store from './store.js'; +import UIRenderer from './renderer.js'; +import { showNotification } from './utils.js'; +import chatMessage from './chatmessage.js'; + +// 全局变量 +let connectionId = ""; +/** + * 绑定DOM事件 + */ +function bindDomEvents() { + // 切换侧边栏 + window.toggleSidebar = function () { + chatMessage.toggleSidebar(); + }; + + // 切换麦克风 + window.toggleMute = function (button) { + const state = store.getState(); + const currentState = state.session.localUser.mediaState.audio; + store.updateLocalMedia('audio', !currentState); + }; + + // 切换视频 + window.toggleVideo = function (button) { + const state = store.getState(); + const currentState = state.session.localUser.mediaState.video; + store.updateLocalMedia('video', !currentState); + }; + + // 切换本地视频(用于悬停控制) + window.toggleLocalVideo = function () { + window.toggleVideo(); + }; + + // 切换录屏 + window.toggleRecording = function (button) { + const state = store.getState(); + const currentState = state.session.localUser.mediaState.recording || false; + store.updateLocalMedia('recording', !currentState); + + // 显示录制状态通知 + if (!currentState) { + showNotification('开始录制'); + } else { + showNotification('停止录制'); + } + }; + + // 更多选项菜单切换 + window.toggleMoreOptions = function () { + const menu = document.getElementById('moreOptionsMenu'); + if (menu) { + menu.classList.toggle('hidden'); + } + }; + + // 切换视频分辨率 + window.changeResolution = function (width, height) { + store.changeResolution(width, height); + // 关闭菜单 + const menu = document.getElementById('moreOptionsMenu'); + if (menu) { + menu.classList.add('hidden'); + } + }; + + // 结束通话 + window.endCall = function () { + // 显示确认对话框 + document.getElementById('endCallDialog').classList.remove('hidden'); + }; + + // 取消结束通话 + window.cancelEndCall = function () { + document.getElementById('endCallDialog').classList.add('hidden'); + }; + + // 确认结束通话 + window.confirmEndCall = function () { + document.getElementById('endCallDialog').classList.add('hidden'); + store.endCall(); + showNotification('通话已结束'); + }; + + // 显示通话请求弹窗 + window.showCallRequest = function (caller) { + const dialog = document.getElementById('callRequestDialog'); + if (dialog) { + // 设置通话请求信息 + if (document.getElementById('callRequestName')) { + document.getElementById('callRequestName').textContent = caller.name; + } + if (document.getElementById('callRequestAvatar')) { + document.getElementById('callRequestAvatar').src = caller.avatar; + } + // 显示弹窗 + dialog.classList.remove('hidden'); + } + }; + + // 拒绝通话 + window.rejectCall = function () { + const dialog = document.getElementById('callRequestDialog'); + if (dialog) { + dialog.classList.add('hidden'); + } + showNotification('已拒绝通话请求'); + // 可以在这里添加发送拒绝通话请求到服务器的逻辑 + }; + + // 接受通话 + window.acceptCall = function () { + const dialog = document.getElementById('callRequestDialog'); + if (dialog) { + dialog.classList.add('hidden'); + } + showNotification('已接受通话请求'); + // 可以在这里添加发送接受通话请求到服务器的逻辑 + // 然后初始化通话 + store.initCall(); + store.setUp(connectionId); + }; + + // 绑定消息相关事件 + chatMessage.bindMessageEvents(); + + // 键盘快捷键 + document.addEventListener('keydown', (event) => { + // 空格键静音 + if (event.code === 'Space' && !event.target.matches('input, textarea')) { + event.preventDefault(); + window.toggleMute(); + } + + // Ctrl+V 切换视频 + if (event.ctrlKey && event.key === 'v') { + event.preventDefault(); + window.toggleVideo(); + } + }); + + // 绑定对话框事件 + document.getElementById('cancelEndCall').addEventListener('click', window.cancelEndCall); + document.getElementById('confirmEndCall').addEventListener('click', window.confirmEndCall); + + // 更多选项按钮事件 + const moreOptionsBtn = document.getElementById('moreOptionsBtn'); + if (moreOptionsBtn) { + moreOptionsBtn.addEventListener('click', window.toggleMoreOptions); + } + + // 点击外部关闭更多选项菜单 + document.addEventListener('click', function(event) { + const moreOptionsMenu = document.getElementById('moreOptionsMenu'); + const moreOptionsBtnEl = document.getElementById('moreOptionsBtn'); + if (moreOptionsMenu && moreOptionsBtnEl && + !moreOptionsMenu.contains(event.target) && + !moreOptionsBtnEl.contains(event.target)) { + moreOptionsMenu.classList.add('hidden'); + } + }); + + // 绑定通话请求对话框事件 + if (document.getElementById('rejectCall')) { + document.getElementById('rejectCall').addEventListener('click', window.rejectCall); + } + if (document.getElementById('acceptCall')) { + document.getElementById('acceptCall').addEventListener('click', window.acceptCall); + } +} + + +// 页面加载完成后初始化 +window.addEventListener('DOMContentLoaded', async () => { + try { + // 检查本地存储中是否有连接ID + const connectionId = localStorage.getItem('connectionId'); + + if (!connectionId) { + // 如果没有连接ID,跳转到连接界面 + window.location.href = './connect/connect.html'; + return; + } + + // 初始化 store + //await store.init(); + + // 初始化渲染器 + const renderer = new UIRenderer(store); + + // 加入通话 + await store.joinCall(connectionId); + + // 设置WebRTC连接 + await store.setUp(connectionId); + + renderer.renderHeaderTitle(); + + // 绑定DOM事件 + bindDomEvents(); + + console.log('Video call app initialized successfully'); + } catch (error) { + console.error('Error initializing app:', error); + showNotification('初始化失败,请刷新页面重试', 'error'); + } +}); + +// 导出全局变量 +export { store }; diff --git a/client/public/onebyone/models.js b/client/public/onebyone/models.js new file mode 100644 index 0000000..c95adda --- /dev/null +++ b/client/public/onebyone/models.js @@ -0,0 +1,112 @@ +/** + * 类型定义和数据模型 + */ + +/** + * @typedef {Object} CallSession + * @property {string} id - 通话唯一标识 (UUID) + * @property {'video'|'audio'} type - 通话类型 + * @property {'connecting'|'ongoing'|'ended'|'failed'} status - 通话状态 + * @property {string} startTime - ISO 8601 时间戳 + * @property {number} duration - 已进行秒数 + * @property {boolean} isEncrypted - 是否启用端到端加密 + * @property {LocalUser} localUser - 本地用户信息 + * @property {RemoteUser} remoteUser - 远端用户信息 + */ + +/** + * @typedef {Object} LocalUser + * @property {string} id - 用户ID + * @property {string} name - 显示名称 + * @property {string} avatar - 头像URL + * @property {boolean} isHost - 是否主持人 + * @property {MediaState} mediaState - 媒体状态 + */ + +/** + * @typedef {Object} RemoteUser + * @property {string} id - 用户ID + * @property {string} name - 显示名称 + * @property {string} avatar - 头像URL + * @property {'online'|'offline'|'connecting'} status - 在线状态 + * @property {MediaState} mediaState - 媒体状态 + * @property {'excellent'|'good'|'fair'|'poor'} networkQuality - 网络质量 + */ + +/** + * @typedef {Object} MediaState + * @property {boolean} audio - 音频是否开启 + * @property {boolean} video - 视频是否开启 + * @property {boolean} screenShare - 是否屏幕共享 + * @property {boolean} recording - 是否正在录屏 + * @property {boolean} isSpeaking - 是否正在说话(VAD) + */ + +/** + * @typedef {Object} ChatMessage + * @property {string} id - 消息唯一ID + * @property {string} senderId - 发送者ID + * @property {string} senderName - 发送者名称 + * @property {string} senderAvatar - 发送者头像URL + * @property {string} content - 消息内容 + * @property {'text'|'file'|'system'} type - 消息类型 + * @property {string} timestamp - ISO 8601 时间戳 + * @property {boolean} isSelf - 是否为自己发送 + */ + +// 模拟通话会话数据 +const mockCallSession = { + id: "call-8842-2024-001", + type: "video", + status: "ongoing", // connecting | ongoing | ended | failed + startTime: "2024-01-15T14:30:00.000Z", + duration: 0, // 秒数,后端可不返回,前端本地计算 + isEncrypted: true, + + // 本地用户信息 + localUser: { + id: "user-local-001", + name: "我", + avatar: "/images/p1.png", + isHost: true, + mediaState: { + audio: true, + video: true, + screenShare: false, + recording: false, + isSpeaking: false + } + }, + + // 远端用户信息 + remoteUser: { + id: "user-remote-002", + name: "Unity", + avatar: "/images/p2.png", + status: "offline", // online | offline | connecting + networkQuality: "no_signal", // excellent | good | fair | poor | no_signal + mediaState: { + audio: true, + video: true, + screenShare: false, + recording: false, + isSpeaking: false + } + } +}; + +// 模拟聊天消息数据 +const mockMessages = [ + { + id: "msg-001", + senderId: "system", + senderName: "系统", + senderAvatar: "/images/screenshot.png", + content: "通话已建立连接", + type: "system", + timestamp: "2024-01-15T14:30:00.000Z", + isSelf: false + } +]; + +export { mockCallSession, mockMessages }; diff --git a/client/public/onebyone/renderer.js b/client/public/onebyone/renderer.js new file mode 100644 index 0000000..5cf1b76 --- /dev/null +++ b/client/public/onebyone/renderer.js @@ -0,0 +1,1125 @@ +/** + * UI渲染器 + * 负责将状态映射到DOM,与状态管理解耦 + */ +import { formatTime, formatTimestamp, toggleElement, toggleButtonState } from './utils.js'; +import { mockCallSession } from './models.js'; +import chatMessage from './chatmessage.js'; +import store from './store.js'; + +const GRID_LAYOUT = { + maxColumns: 3, + breakpoints: [ + { maxParticipants: 1, template: '1fr' }, + { maxParticipants: 4, template: 'repeat(2, 1fr)' } + ], + defaultTemplate: 'repeat(3, 1fr)' +}; + +function getGridTemplateColumns(participantCount) { + for (const bp of GRID_LAYOUT.breakpoints) { + if (participantCount <= bp.maxParticipants) { + return bp.template; + } + } + return GRID_LAYOUT.defaultTemplate; +} + +class UIRenderer { + constructor(stateManager) { + this.stateManager = stateManager; + this.unsubscribe = stateManager.subscribe(this.render.bind(this)); + + // 缓存 DOM 元素 + this.elements = { + // 头部和底部 + header: document.querySelector('header'), + footer: document.querySelector('footer'), + // 多Participant视频网格 + participantGrid: document.getElementById('participantGrid'), + // 头部内容 + headerTitle: document.getElementById('headerTitle'), + callDuration: document.getElementById('callDuration'), + encryptionBadge: document.getElementById('encryptionBadge'), + unreadBadge: document.getElementById('unreadBadge'), + remoteNetworkIndicator: document.getElementById('remoteNetworkIndicator'), + remoteNetworkQuality: document.getElementById('remoteNetworkQuality'), + + // 远端视频 + remoteVideo: document.getElementById('remoteVideo'), + remoteVideoPlaceholder: document.getElementById('remoteVideoPlaceholder'), + networkStatus: document.getElementById('networkStatus'), + networkStatusText: document.getElementById('networkStatusText'), + connectingOverlay: document.getElementById('connectingOverlay'), + + // 本地视频 + localVideo: document.getElementById('localVideo'), + localVideoPlaceholder: document.getElementById('localVideoPlaceholder'), + localAudioWave: document.getElementById('localAudioWave'), + localInitials: document.getElementById('localInitials'), + + // 侧边栏 + sidebar: document.getElementById('sidebar'), + chatContent: document.getElementById('chatContent'), + userList: document.getElementById('userList'), + localMediaStatus: document.getElementById('localMediaStatus'), + localMuteIcon: document.querySelector('[data-field="localUser.muteIcon"]'), + userCountDisplay: document.getElementById('userCountDisplay'), + // 控制按钮 + micBtn: document.getElementById('micBtn'), + videoBtn: document.getElementById('videoBtn'), + recordBtn: document.getElementById('recordBtn'), + connectionQuality: document.getElementById('connectionQuality') + }; + + // 绑定事件监听器 + this.bindEventListeners(); + + // 订阅状态变化 + this.unsubscribe = stateManager.subscribe(this.render.bind(this)); + // 订阅消息状态变化 + this.messageUnsubscribe = chatMessage.subscribe(this.renderMessageState.bind(this)); + // 初始化渲染 + this.render(this.stateManager.getState(), { type: 'INIT' }); + + window.addEventListener('resize', () => { + if (this.elements.remoteVideo && this.elements.remoteVideo.srcObject) { + const stream = this.elements.remoteVideo.srcObject; + const videoTracks = stream.getVideoTracks(); + if (videoTracks.length > 0) { + const resolution = this.getVideoResolution(videoTracks[0]); + this.adjustVideoSize(this.elements.remoteVideo, resolution); + } + } + }); + } + + // 渲染消息状态变化 + renderMessageState(messageState, changes) { + switch (changes.type) { + case 'NEW_MESSAGE': + this.renderChatMessages(messageState.messages); + this.renderUnreadCount(changes.unreadCount); + break; + case 'SIDEBAR_TOGGLE': + this.renderSidebar(changes.isOpen); + // 当侧边栏打开时,重置未读消息计数 + if (changes.isOpen) { + this.renderUnreadCount(0); + } else { + this.renderUnreadCount(changes.unreadCount); + } + break; + } + } + + // 绑定事件监听器 + bindEventListeners() { + // 事件监听器 + } + + + /** + * 渲染方法 - 根据状态变化更新UI + * @param {Object} state - 当前应用状态 + * @param {Object} changes - 状态变化对象 + */ + render(state, changes) { + // 根据变化类型执行不同的渲染操作 + switch (changes.type) { + case 'INIT': + // 初始化渲染 - 渲染所有UI元素 + this.renderRemoteVideo(state.session.remoteUser); // 渲染远程视频 + this.renderLocalVideo(state.session.localUser, state.localStream); // 渲染本地视频 + this.renderControlButtons(state.session.localUser.mediaState); // 渲染控制按钮 + this.renderChatMessages(chatMessage.getMessageState().messages); // 渲染聊天消息 + this.renderUserList(state.session.localUser, state.session.remoteUser, state.participants); // 渲染用户列表 + this.renderHeader(state.session); // 渲染头部信息 + // 初始化时检查远程流状态,显示或隐藏占位背景 + if (this.elements.remoteVideoPlaceholder) { + if (state.remoteStream) { + this.elements.remoteVideoPlaceholder.classList.add('hidden'); // 有远程流时隐藏占位背景 + } else { + this.elements.remoteVideoPlaceholder.classList.remove('hidden'); // 无远程流时显示占位背景 + } + } + break; + case 'DURATION_UPDATE': + // 通话时长更新 - 渲染通话时长 + this.renderCallDuration(changes.duration); + break; + case 'LOCAL_MEDIA_CHANGE': + // 本地媒体状态变化 - 更新相关UI + this.renderControlButtons(state.session.localUser.mediaState); // 渲染控制按钮 + this.renderLocalVideo(state.session.localUser, state.localStream); // 渲染本地视频 + this.renderLocalUserStatus(state.session.localUser); // 渲染本地用户状态 + this.renderUserList(state.session.localUser, state.session.remoteUser, state.participants); // 渲染用户列表 + break; + case 'LOCAL_STREAM_OBTAINED': + this.renderLocalStream(state.localStream); + this.renderLocalVideo(state.session.localUser, state.localStream); + break; + case 'REMOTE_STREAM_OBTAINED': + // 远程流获取成功 - 更新远程视频显示 + this.renderRemoteStream(changes.stream, changes.connectionId, changes.isHost); // 渲染远程流 + // 当获取到远程流时,隐藏连接中提示 + if (this.elements.connectingOverlay) { + this.elements.connectingOverlay.classList.add('hidden'); + } + break; + case 'REMOTE_MEDIA_CHANGE': + // 远程媒体状态变化 - 更新远程视频和用户列表 + this.renderRemoteVideo(state.session.remoteUser); // 渲染远程视频 + this.renderUserList(state.session.localUser, state.session.remoteUser, state.participants); // 渲染用户列表 + // Host端:精准更新发送者participant tile的占位背景 + // 只有Host端需要处理participantId对应的tile占位符(Participant端没有其他Participant的视频流) + if (changes.participantId && state.session.localUser.isHost) { + // 从participants Map中读取该participant的video状态,而非remoteUser(多Participant场景remoteUser不精确) + const pInfo = state.participants[changes.participantId]; + const showPlaceholder = pInfo ? !pInfo.mediaState.video : true; + this.renderParticipantVideoPlaceholder(changes.participantId, showPlaceholder); + } + break; + case 'USER_LIST_UPDATE': + // 用户列表更新 - 重新渲染用户列表 + this.renderUserList(changes.localUser, changes.remoteUser, state.participants); + break; + case 'PARTICIPANTS_UPDATE': + // Participants信息变化 - 重新渲染用户列表并同步tile名称 + this.renderUserList(state.session.localUser, state.session.remoteUser, changes.participants || state.participants); + // 同步更新participant tile的名称标签 + this.syncParticipantTileNames(changes.participants || state.participants); + break; + case 'NETWORK_CHANGE': + // 网络状态变化 - 渲染网络状态 + this.renderNetworkStatus(changes.quality); + break; + case 'CALL_STATUS_CHANGE': + // 通话状态变化 - 渲染通话状态 + this.renderCallStatus(changes.status); + break; + case 'CALL_ENDED': + // 通话结束 - 渲染通话结束界面 + this.renderCallEnded(); + break; + case 'PARTICIPANT_LEFT': + // participant离开 - 更新UI但房间仍然存在 + this.renderParticipantLeft(changes.connectionId); + break; + case 'RESOLUTION_CHANGED': + // 分辨率变化 - 更新UI中的分辨率选中状态 + this.renderResolutionChanged(changes.resolution); + break; + } + } + + // 渲染通话状态 + renderCallStatus(status) { + if (this.elements.connectingOverlay) { + if (status === 'connecting') { + this.elements.connectingOverlay.classList.remove('hidden'); + } else { + this.elements.connectingOverlay.classList.add('hidden'); + } + } + } + + // 渲染头部 + renderHeader(session) { + + this.renderHeaderTitle(); + if (this.elements.encryptionBadge) { + toggleElement(this.elements.encryptionBadge, session.isEncrypted); + } + + // 始终显示网络状态指示器和质量 + if (this.elements.remoteNetworkIndicator) { + this.elements.remoteNetworkIndicator.classList.remove('hidden'); + } + if (this.elements.remoteNetworkQuality) { + this.elements.remoteNetworkQuality.classList.remove('hidden'); + } + + this.renderCallDuration(session.duration); + } + renderHeaderTitle() { + if (this.elements.headerTitle) { + const connectionId = store.getConnectionId() || ''; + // 未连接时不显示红框部分 + this.elements.headerTitle.textContent = `通话 (${connectionId})`; + + } + } + + // 渲染通话时长 + renderCallDuration(duration) { + if (this.elements.callDuration) { + this.elements.callDuration.textContent = formatTime(duration); + } + } + + // 渲染分辨率变化 + renderResolutionChanged(resolution) { + if (!resolution) return; + + // 更新分辨率选项的选中状态 + const options = document.querySelectorAll('.resolution-option'); + options.forEach(btn => { + const btnRes = btn.dataset.resolution; + const isActive = (resolution.height >= 1440 && btnRes === '1440') || + (resolution.height >= 1080 && resolution.height < 1440 && btnRes === '1080') || + (resolution.height >= 720 && resolution.height < 1080 && btnRes === '720') || + (resolution.height < 720 && btnRes === '480'); + btn.classList.toggle('active', isActive); + }); + + // 更新当前分辨率文本 + const currentResText = document.getElementById('currentResolutionText'); + if (currentResText) { + currentResText.textContent = `当前: ${resolution.label}`; + } + } + + // 渲染远端视频 + renderRemoteVideo(remoteUser) { + // 同步更新侧边栏用户列表 + this.renderUserList(this.stateManager.getState().session.localUser, remoteUser, this.stateManager.getState().participants); + + // 当远程视频关闭时显示占位符 + if (this.elements.remoteVideoPlaceholder) { + const shouldShowPlaceholder = !remoteUser.mediaState.video; + toggleElement(this.elements.remoteVideoPlaceholder, shouldShowPlaceholder); + + // 更新占位符文本内容 + if (shouldShowPlaceholder) { + const placeholderContent = this.elements.remoteVideoPlaceholder.querySelector('.text-center'); + if (placeholderContent) { + const titleElement = placeholderContent.querySelector('p.text-white.text-lg.font-medium'); + if (titleElement) { + titleElement.textContent = '对方摄像头已关闭'; + } + const subtitleElement = placeholderContent.querySelector('p.text-sm.text-gray-400'); + if (subtitleElement) { + subtitleElement.textContent = '对方暂时关闭了视频'; + } + } + } else { + // 恢复默认占位符文本 + const placeholderContent = this.elements.remoteVideoPlaceholder.querySelector('.text-center'); + if (placeholderContent) { + const titleElement = placeholderContent.querySelector('p.text-white.text-lg.font-medium'); + if (titleElement) { + titleElement.textContent = '等待对方连接...'; + } + const subtitleElement = placeholderContent.querySelector('p.text-sm.text-gray-400'); + if (subtitleElement) { + subtitleElement.textContent = '请确保对方已加入通话'; + } + } + } + } + + // 渲染网络状态 + this.renderNetworkStatus(remoteUser.networkQuality); + + // 渲染header中的网络状态 + this.renderHeaderNetworkStatus(remoteUser.networkQuality); + } + + // 渲染header中的网络状态 + renderHeaderNetworkStatus(networkQuality) { + if (this.elements.remoteNetworkQuality) { + const textElement = this.elements.remoteNetworkQuality.querySelector('span'); + const iconElement = this.elements.remoteNetworkQuality.querySelector('i'); + + if (textElement && iconElement) { + let qualityText = '未知'; + let iconClass = 'fas fa-signal text-gray-400'; + + switch (networkQuality) { + case 'excellent': + qualityText = '优秀'; + iconClass = 'fas fa-signal text-green-400'; + break; + case 'good': + qualityText = '良好'; + iconClass = 'fas fa-signal text-green-500'; + break; + case 'fair': + qualityText = '一般'; + iconClass = 'fas fa-signal text-yellow-400'; + break; + case 'poor': + qualityText = '较差'; + iconClass = 'fas fa-signal text-red-400'; + break; + } + + textElement.textContent = qualityText; + iconElement.className = iconClass; + } + } + } + + // 渲染本地视频 + renderLocalVideo(localUser, localStream) { + if (this.elements.localVideoPlaceholder) { + // 当没有视频流或视频关闭时显示占位符 + const shouldShowPlaceholder = !localStream || !localUser.mediaState.video; + toggleElement(this.elements.localVideoPlaceholder, shouldShowPlaceholder); + } + + if (this.elements.localAudioWave) { + toggleElement(this.elements.localAudioWave, localUser.mediaState.isSpeaking); + } + + // 同时渲染本地用户状态 + this.renderLocalUserStatus(localUser); + } + + // 渲染本地视频流 + renderLocalStream(stream) { + if (this.elements.localVideo && stream) { + this.elements.localVideo.srcObject = stream; + this.elements.localVideo.autoplay = true; + this.elements.localVideo.muted = true; // 本地视频静音,避免回声 + console.log('srcObject set successfully:', this.elements.localVideo.srcObject); + + // 隐藏断开连接覆盖层 + if (this.elements.disconnectedOverlay) { + this.elements.disconnectedOverlay.classList.add('hidden'); + } + } else { + console.error('Either localVideo element or stream is missing'); + } + } + + // 渲染远程视频流 + renderRemoteStream(stream, connectionId, isHost) { + if (isHost && connectionId) { + // Host端: 渲染到 participant 视频网格 + this.renderParticipantStream(stream, connectionId); + } else { + // Participant端: 渲染到单一远端视频(Host的画面) + this.renderSingleRemoteStream(stream); + } + } + + // 渲染Host端的多Participant视频网格 + // 每个participant tile显示该participant实际的远端视频流 + renderParticipantStream(stream, connectionId) { + const grid = this.elements.participantGrid; + if (!grid) return; + + // 显示网格,隐藏单路远端视频 + grid.classList.remove('hidden'); + + // 查找或创建该 connectionId 的视频格子 + let tile = grid.querySelector(`[data-participant-id="${connectionId}"]`); + if (!tile) { + tile = document.createElement('div'); + tile.className = 'relative bg-black/60 rounded-xl overflow-hidden flex items-center justify-center'; + tile.dataset.participantId = connectionId; + + const video = document.createElement('video'); + video.className = 'w-full h-full object-contain'; + video.autoplay = true; + video.playsinline = true; + video.muted = false; // 不静音,播放participant的音频 + video.id = `participantVideo_${connectionId}`; + tile.appendChild(video); + + // 参与者视频关闭时的占位背景(复用 remoteVideoPlaceholder 样式) + const placeholder = document.createElement('div'); + placeholder.className = 'participant-video-placeholder absolute inset-0 flex items-center justify-center bg-gradient-to-br from-indigo-900/80 to-purple-900/80 hidden'; + placeholder.innerHTML = ` +
    +
    + +
    +

    摄像头已关闭

    +
    + `; + tile.appendChild(placeholder); + + // 参与者名称标签(优先使用participants中的真实姓名) + const pInfo = this.stateManager.getState().participants[connectionId]; + const displayName = pInfo?.name || '参与者'; + const label = document.createElement('div'); + label.className = 'absolute bottom-3 left-3 glass px-3 py-1 rounded-full text-xs flex items-center gap-2'; + label.innerHTML = `${displayName}`; + tile.appendChild(label); + + // 在线标识 + const liveTag = document.createElement('div'); + liveTag.className = 'absolute top-3 right-3 bg-green-500/80 px-2 py-0.5 rounded-full text-xs flex items-center gap-1'; + liveTag.innerHTML = `在线`; + tile.appendChild(liveTag); + + grid.appendChild(tile); + console.log(`Created participant video tile for ${connectionId}`); + } + + if (tile) { + // 视频元素显示participant的远端视频流 + const video = tile.querySelector('video'); + if (video && stream) { + // 避免重复设置同一流对象(音频先到、视频后到时流对象相同) + if (video.srcObject === stream) { + console.log(`Same stream for participant ${connectionId}, ensuring playback`); + video.play().catch(e => console.log('Auto-play prevented:', e.message)); + } else { + video.srcObject = stream; + video.play().catch(e => console.log('Auto-play prevented:', e.message)); + console.log(`Set remote stream for participant tile ${connectionId}`); + } + } + + // 隐藏单路远端视频和占位符 + const remoteVideoDiv = this.elements.remoteVideo?.closest('.absolute.inset-0.video-fade-in'); + if (remoteVideoDiv) { + remoteVideoDiv.classList.add('hidden'); + } + } + + // 根据参与者数量调整网格列数 + const tileCount = grid.querySelectorAll('[data-participant-id]').length; + grid.style.gridTemplateColumns = getGridTemplateColumns(tileCount); + + // 隐藏连接中提示 + if (this.elements.connectingOverlay) { + this.elements.connectingOverlay.classList.add('hidden'); + } + + // 隐藏远端视频占位符 + if (this.elements.remoteVideoPlaceholder) { + this.elements.remoteVideoPlaceholder.classList.add('hidden'); + } + } + + // 精准更新指定participant tile的占位背景 + // participantId: 发送media-state-changed的participant的连接ID + // showPlaceholder: 是否显示占位背景(视频关闭时为true) + renderParticipantVideoPlaceholder(participantId, showPlaceholder) { + const grid = this.elements.participantGrid; + if (!grid) return; + const tile = grid.querySelector(`[data-participant-id="${participantId}"]`); + if (!tile) return; + const placeholder = tile.querySelector('.participant-video-placeholder'); + if (placeholder) { + toggleElement(placeholder, showPlaceholder); + console.log(`Updated placeholder for participant ${participantId}: ${showPlaceholder ? 'shown' : 'hidden'}`); + } + } + + // 同步更新所有participant tile的名称标签 + syncParticipantTileNames(participants) { + if (!participants) return; + const grid = this.elements.participantGrid; + if (!grid) return; + for (const [participantId, pInfo] of Object.entries(participants)) { + this.updateParticipantTileName(participantId, pInfo.name); + } + } + + // 更新指定participant tile的名称标签 + updateParticipantTileName(participantId, name) { + const grid = this.elements.participantGrid; + if (!grid) return; + const tile = grid.querySelector(`[data-participant-id="${participantId}"]`); + if (!tile) return; + const label = tile.querySelector('.absolute.bottom-3'); + if (label) { + const nameSpan = label.querySelector('span'); + if (nameSpan && name) { + nameSpan.textContent = name; + console.log(`Updated tile name for participant ${participantId}: ${name}`); + } + } + } + + // 渲染Participant端的单一远端视频(Host画面) + renderSingleRemoteStream(stream) { + if (!this.elements.remoteVideo || !stream) { + console.error('Either remoteVideo element or stream is missing'); + return; + } + + console.log('Rendering remote stream:', stream, 'tracks:', stream.getTracks().map(t => `${t.kind}(${t.readyState})`)); + + // 关键修复:避免 srcObject = null 的重置模式 + // 如果 srcObject 已经是同一个 stream 对象,说明是同一流的轨道更新(如音频先到,视频后到) + // 浏览器会自动识别新添加的轨道,无需重置 srcObject + if (this.elements.remoteVideo.srcObject === stream) { + console.log('Same stream object, track added - ensuring playback'); + this.elements.remoteVideo.play().catch(e => console.log('Auto-play prevented:', e.message)); + return; + } + + // 首次设置或流对象变化:直接设置 srcObject(不使用 null 重置模式) + this.elements.remoteVideo.srcObject = stream; + this.elements.remoteVideo.autoplay = true; + this.elements.remoteVideo.playsinline = true; + this.elements.remoteVideo.muted = false; + + // 确保视频开始播放 + this.elements.remoteVideo.play().catch(e => { + console.log('Auto-play prevented, will retry on interaction:', e.message); + }); + + // 隐藏断开连接覆盖层 + if (this.elements.disconnectedOverlay) { + this.elements.disconnectedOverlay.classList.add('hidden'); + } + + // 监听视频轨道变化 + const videoTracks = stream.getVideoTracks(); + const audioTracks = stream.getAudioTracks(); + console.log(`Stream has ${videoTracks.length} video tracks, ${audioTracks.length} audio tracks`); + + if (videoTracks.length > 0) { + // 有视频轨道:隐藏占位符 + if (this.elements.remoteVideoPlaceholder) { + this.elements.remoteVideoPlaceholder.classList.add('hidden'); + } + if (this.elements.connectingOverlay) { + this.elements.connectingOverlay.classList.add('hidden'); + } + + // 监听视频轨道分辨率变化 + const activeVideoTrack = videoTracks.find(track => track.readyState === 'live'); + if (activeVideoTrack) { + const resolution = this.getVideoResolution(activeVideoTrack); + this.adjustVideoSize(this.elements.remoteVideo, resolution); + activeVideoTrack.addEventListener('resize', () => { + const newResolution = this.getVideoResolution(activeVideoTrack); + this.adjustVideoSize(this.elements.remoteVideo, newResolution); + }); + } + } else { + // 只有音频轨道(视频轨道尚未到达):不显示占位符,等待视频轨道到达 + // 不设置 srcObject = null,保持音频播放 + console.log('Audio-only stream, waiting for video track...'); + } + } + + // 渲染本地用户状态 + renderLocalUserStatus(localUser) { + // 更新本地媒体状态文本 + if (this.elements.localMediaStatus) { + if (!localUser.mediaState.audio) { + this.elements.localMediaStatus.textContent = '静音中'; + this.elements.localMediaStatus.className = 'text-xs text-gray-500'; + } else if (!localUser.mediaState.video) { + this.elements.localMediaStatus.textContent = '视频关闭'; + this.elements.localMediaStatus.className = 'text-xs text-gray-500'; + } else { + this.elements.localMediaStatus.textContent = '在线'; + this.elements.localMediaStatus.className = 'text-xs text-green-400'; + } + } + + // 更新静音图标 + if (this.elements.localMuteIcon) { + if (!localUser.mediaState.audio) { + this.elements.localMuteIcon.classList.remove('hidden'); + this.elements.localMuteIcon.className = 'fas fa-microphone-slash text-gray-500 text-xs'; + } else { + this.elements.localMuteIcon.classList.add('hidden'); + } + } + } + + // 渲染侧边栏用户列表(支持多Participant动态渲染) + renderUserList(localUser, remoteUser, participants) { + if (!this.elements.userList) return; + + const participantsMap = participants || {}; + const participantCount = Object.keys(participantsMap).length; + + // 通话成员总数 = 本地用户(1) + participants中的条目数 + // Host端participants只含其他participant;Participant端participants含host+其他participant + const userCount = 1 + participantCount; + + // 更新通话成员总数显示 + const userCountElement = this.elements.userCountDisplay; + if (userCountElement) { + userCountElement.textContent = `通话成员 (${userCount})`; + } + + // 清空列表并重新渲染 + this.elements.userList.innerHTML = ''; + + // 1. 渲染本地用户 + // 判断当前用户角色:Host端localUser是主持人;Participant端localUser是参与者 + this.elements.userList.appendChild(this.createUserEntry({ + user: localUser, + role: 'local' + })); + + // 2. 渲染远端成员 + if (participantCount > 0) { + // 有participants数据(Host端或Participant端收到participants-sync后) + for (const [pid, p] of Object.entries(participantsMap)) { + if (p.role === 'host') { + this.elements.userList.appendChild(this.createUserEntry({ + user: p, + role: 'host', + id: pid + })); + } else { + this.elements.userList.appendChild(this.createUserEntry({ + user: p, + role: 'participant', + id: pid + })); + } + } + } else if (remoteUser.status !== 'offline') { + // 兼容:Participant端未收到participants-sync时,使用remoteUser显示Host + this.elements.userList.appendChild(this.createUserEntry({ + user: remoteUser, + role: 'remote' + })); + } + } + + // 创建通用用户条目 + createUserEntry(options) { + const { user, role, id } = options; + + const div = document.createElement('div'); + const baseClass = 'flex items-center gap-3 p-2 rounded-lg'; + div.className = role === 'local' + ? `${baseClass} hover:bg-white/5` + : `${baseClass} bg-white/5`; + + // dataset.userId + switch (role) { + case 'local': + div.dataset.userId = 'local'; + break; + case 'remote': + div.dataset.userId = 'remote'; + break; + case 'host': + div.dataset.userId = `host_${id}`; + break; + case 'participant': + div.dataset.userId = `participant_${id}`; + break; + } + + const mediaState = user.mediaState; + const mediaStatusText = !mediaState.audio ? '静音中' : (!mediaState.video ? '视频关闭' : '在线'); + const mediaStatusClass = (!mediaState.audio || !mediaState.video) ? 'text-xs text-gray-500' : 'text-xs text-green-400'; + const muteIconHtml = !mediaState.audio + ? '' + : ''; + + // 头像区域 + let avatarHtml; + if (role === 'local') { + avatarHtml = ``; + } else { + avatarHtml = ` +
    + +
    +
    + `; + } + + // 角色标签 + let roleTag; + if (role === 'local') { + const isHost = user.isHost; + roleTag = isHost + ? '主持人' + : '参与者'; + } else if (role === 'participant') { + roleTag = '参与者'; + } else { + // remote, host + roleTag = '主持人'; + } + + // 媒体状态 data-field(仅local) + const dataFieldAttr = role === 'local' ? ' data-field="localUser.mediaStatus"' : ''; + + // 右侧内容 + let rightHtml; + if (role === 'participant') { + const speakingHtml = (mediaState.isSpeaking && mediaState.audio) + ? '
    ' + : ''; + rightHtml = ` +
    + ${muteIconHtml} + ${speakingHtml} +
    + `; + } else { + rightHtml = muteIconHtml; + } + + div.innerHTML = ` + ${avatarHtml} +
    +
    + ${user.name} + ${roleTag} +
    +
    ${mediaStatusText}
    +
    + ${rightHtml} + `; + + return div; + } + // 在renderer.js中添加方法 + // 获取视频流分辨率 + getVideoResolution(track) { + if (track && track.getSettings) { + const settings = track.getSettings(); + return { + width: settings.width || 640, + height: settings.height || 480 + }; + } + return { width: 640, height: 480 }; // 默认值 + } + + // 调整视频元素大小并居中显示 + adjustVideoSize(videoElement, resolution) { + if (!videoElement) return; + + const { width, height } = resolution; + const aspectRatio = width / height; + + // 根据容器大小和视频宽高比调整视频显示 + const container = videoElement.parentElement; + const containerWidth = container.clientWidth; + const containerHeight = container.clientHeight; + // 启用硬件加速 + videoElement.style.transform = 'translateZ(0)'; + videoElement.style.willChange = 'transform'; + // 设置容器为flex布局,使视频居中 + container.style.display = 'flex'; + container.style.alignItems = 'center'; + container.style.justifyContent = 'center'; + // 优化图像渲染 + videoElement.style.imageRendering = 'auto'; + // 确保视频元素在容器内正确显示 + videoElement.style.maxWidth = '100%'; + videoElement.style.maxHeight = '100%'; + videoElement.style.objectFit = 'contain'; // 保持原始比例,不裁剪 + } + // 渲染控制按钮 + renderControlButtons(mediaState) { + if (this.elements.micBtn) { + toggleButtonState(this.elements.micBtn, !mediaState.audio); + } + + if (this.elements.videoBtn) { + toggleButtonState(this.elements.videoBtn, !mediaState.video); + } + + if (this.elements.recordBtn) { + toggleButtonState(this.elements.recordBtn, mediaState.recording); + } + } + + // 渲染聊天消息 + renderChatMessages(messages) { + if (!this.elements.chatContent) return; + + // 清空聊天内容 + this.elements.chatContent.innerHTML = ''; + + // 添加通话开始时间 + const startTimeElement = document.createElement('div'); + startTimeElement.className = 'text-center text-xs text-gray-500 my-4'; + const startTime = messages[0]?.timestamp || new Date().toISOString(); + startTimeElement.textContent = `通话开始 ${formatTimestamp(startTime)}`; + this.elements.chatContent.appendChild(startTimeElement); + + // 添加消息 + messages.forEach(message => { + const messageElement = this.createMessageElement(message); + this.elements.chatContent.appendChild(messageElement); + }); + + // 滚动到底部 + this.elements.chatContent.scrollTop = this.elements.chatContent.scrollHeight; + } + + // 创建消息元素 + createMessageElement(message) { + const messageDiv = document.createElement('div'); + + // 根据消息类型设置不同的CSS类 + let messageClass = 'chat-bubble'; + if (message.type === 'system') { + messageClass += ' message-system'; + } else if (message.isSelf) { + messageClass += ' message-self'; + } else { + messageClass += ' message-other'; + } + + messageDiv.className = messageClass; + messageDiv.dataset.messageId = message.id; + + let contentHTML = ''; + if (message.type === 'file' && message.content.startsWith('data:image/')) { + // 图片消息 + contentHTML = ` +
    + ${message.fileName || '图片'} + ${message.fileName ? `
    ${message.fileName}
    ` : ''} +
    + `; + } else { + // 文本消息 + contentHTML = ` +
    + ${message.content} +
    + `; + } + + messageDiv.innerHTML = ` +
    + +
    + ${message.senderName} + ${formatTimestamp(message.timestamp)} +
    +
    +
    + ${contentHTML} +
    + `; + + return messageDiv; + } + + // 渲染未读消息数 + renderUnreadCount(count) { + if (this.elements.unreadBadge) { + if (count > 0) { + this.elements.unreadBadge.textContent = count; + this.elements.unreadBadge.classList.remove('hidden'); + } else { + this.elements.unreadBadge.classList.add('hidden'); + } + } + } + + // 渲染侧边栏 + renderSidebar(isOpen) { + if (this.elements.sidebar) { + if (isOpen) { + this.elements.sidebar.classList.remove('hidden'); + } else { + this.elements.sidebar.classList.add('hidden'); + } + } + } + + // 渲染网络状态 + renderNetworkStatus(quality) { + if (this.elements.networkStatus && this.elements.networkStatusText) { + // 始终显示网络状态 + toggleElement(this.elements.networkStatus, true); + + // 根据网络质量设置不同的图标和颜色 + const networkStatus = this.elements.networkStatus; + const networkStatusText = this.elements.networkStatusText; + + // 清除之前的图标 + const existingIcon = networkStatus.querySelector('i'); + if (existingIcon) { + existingIcon.remove(); + } + + // 创建新的图标元素 + const icon = document.createElement('i'); + + // 根据网络质量设置图标和样式 + switch (quality) { + case 'excellent': + icon.className = 'fas fa-check-circle text-green-400'; + networkStatusText.textContent = this.getNetworkQualityText(quality); + networkStatusText.className = 'text-green-400'; + break; + case 'good': + icon.className = 'fas fa-signal text-blue-400'; + networkStatusText.textContent = this.getNetworkQualityText(quality); + networkStatusText.className = 'text-blue-400'; + break; + case 'fair': + icon.className = 'fas fa-exclamation-circle text-yellow-500'; + networkStatusText.textContent = this.getNetworkQualityText(quality); + networkStatusText.className = 'text-yellow-500'; + break; + case 'poor': + icon.className = 'fas fa-exclamation-triangle text-red-500'; + networkStatusText.textContent = this.getNetworkQualityText(quality); + networkStatusText.className = 'text-red-500'; + break; + case 'no_signal': + icon.className = 'fas fa-times-circle text-gray-500'; + networkStatusText.textContent = this.getNetworkQualityText(quality); + networkStatusText.className = 'text-gray-500'; + break; + default: + icon.className = 'fas fa-question-circle text-gray-400'; + networkStatusText.textContent = '未知'; + networkStatusText.className = 'text-gray-400'; + } + + // 添加图标到网络状态元素 + networkStatus.insertBefore(icon, networkStatusText); + } + + if (this.elements.connectionQuality) { + const qualityText = this.getNetworkQualityText(quality); + let statusClass = ''; + + // 根据网络质量设置文本颜色 + switch (quality) { + case 'excellent': + statusClass = 'text-green-400'; + break; + case 'good': + statusClass = 'text-blue-400'; + break; + case 'fair': + statusClass = 'text-yellow-500'; + break; + case 'poor': + statusClass = 'text-red-500'; + break; + case 'no_signal': + statusClass = 'text-gray-500'; + break; + default: + statusClass = 'text-gray-400'; + } + + // 更新连接质量文本和样式 + this.elements.connectionQuality.textContent = `连接质量: ${qualityText}`; + this.elements.connectionQuality.className = `text-xs ${statusClass}`; + } + + // 同步更新头部网络指示器 + this.updateHeaderNetworkIndicator(quality); + + // 同步更新头部网络质量文本 + this.renderHeaderNetworkStatus(quality); + } + + // 更新头部网络指示器 + updateHeaderNetworkIndicator(networkQuality) { + if (!this.elements.remoteNetworkIndicator) return; + + // 根据网络质量设置指示器颜色 + if (networkQuality === 'no_signal') { + // 无信号时显示灰色点,取消动画 + this.elements.remoteNetworkIndicator.className = 'w-2 h-2 bg-gray-500 rounded-full'; + } else { + // 有信号时显示绿色点,保持动画 + this.elements.remoteNetworkIndicator.className = 'w-2 h-2 bg-green-500 rounded-full animate-pulse'; + } + } + + + // 渲染通话结束 + renderCallEnded() { + console.log('Call ended'); + + // 清理participant网格 + const grid = this.elements.participantGrid; + if (grid) { + grid.querySelectorAll('[data-participant-id]').forEach(tile => { + const video = tile.querySelector('video'); + if (video) video.srcObject = null; + tile.remove(); + }); + grid.classList.add('hidden'); + } + + // 跳转到结束通话界面 + window.location.href = './endcall/endcall.html'; + } + + // 渲染participant离开(host端,房间仍然存在) + renderParticipantLeft(connectionId) { + console.log(`Participant left: ${connectionId}, updating UI`); + + const grid = this.elements.participantGrid; + if (grid) { + const tile = grid.querySelector(`[data-participant-id="${connectionId}"]`); + if (tile) { + const video = tile.querySelector('video'); + if (video) video.srcObject = null; + tile.remove(); + console.log(`Removed participant video tile for ${connectionId}`); + } + + const remainingTiles = grid.querySelectorAll('[data-participant-id]'); + if (remainingTiles.length === 0) { + grid.classList.add('hidden'); + const remoteVideoDiv = this.elements.remoteVideo?.closest('.absolute.inset-0.video-fade-in'); + if (remoteVideoDiv) { + remoteVideoDiv.classList.remove('hidden'); + } + if (this.elements.remoteVideoPlaceholder) { + this.elements.remoteVideoPlaceholder.classList.remove('hidden'); + } + } else { + grid.style.gridTemplateColumns = getGridTemplateColumns(remainingTiles.length); + } + } + + if (this.elements.remoteNetworkIndicator) { + this.elements.remoteNetworkIndicator.className = 'w-2 h-2 bg-gray-500 rounded-full'; + } + } + + // 获取状态文本 + getStatusText(status) { + const statusMap = { + 'online': '在线', + 'offline': '离线', + 'connecting': '连接中' + }; + return statusMap[status] || status; + } + + // 获取网络质量文本 + getNetworkQualityText(quality) { + const qualityMap = { + 'excellent': '优秀', + 'good': '良好', + 'fair': '一般', + 'poor': '较差', + 'no_signal': '无信号' + }; + return qualityMap[quality] || quality; + } + + // 销毁 + destroy() { + if (this.unsubscribe) { + this.unsubscribe(); + } + if (this.messageUnsubscribe) { + this.messageUnsubscribe(); + } + } +} + +export default UIRenderer; diff --git a/client/public/onebyone/store.js b/client/public/onebyone/store.js new file mode 100644 index 0000000..b5d6978 --- /dev/null +++ b/client/public/onebyone/store.js @@ -0,0 +1,1430 @@ +/** + * 状态管理 + * 使用简单的 Observable 模式,可替换为 Redux/Vuex/Pinia + */ +import { mockCallSession } from './models.js'; +import { Signaling, WebSocketSignaling } from "../../module/signaling.js";// 信令管理 +import { RenderStreaming } from "../../module/renderstreaming.js"; // WebRTC连接管理 +import { getServerConfig, getRTCConfiguration } from "../js/config.js";//服务器配置和RTC配置 +import { showNotification, generateId } from './utils.js'; // 导入通知函数 +import chatMessage from './chatmessage.js'; + +const AUDIO_CONFIG = { + echoCancellation: true, + noiseSuppression: true, + autoGainControl: true +}; + +const VAD_CONFIG = { + threshold: 15, + debounceTime: 500, + fftSize: 256 +}; + +const MEDIA_CONSTRAINTS = { + video: { + width: { ideal: 1920, max: 1920 }, + height: { ideal: 1080, max: 1080 }, + frameRate: { ideal: 30, max: 30 } + }, + audio: AUDIO_CONFIG +}; + +const VIDEO_ONLY_CONSTRAINT = { + video: { + width: { ideal: 1920, max: 1920 }, + height: { ideal: 1080, max: 1080 }, + frameRate: { ideal: 30, max: 30 } + }, + audio: false +}; + +class CallStateManager { + constructor() { + // 核心状态 + this.state = { + id: generateId(), + session: { + ...mockCallSession, + status: 'idle' // 初始状态为空闲 + }, + localStream: null, // MediaStream 对象 + remoteStream: null, // 单路远端流(兼容旧逻辑,participant端使用) + remoteStreams: {}, // 多路远端流 Map: { connectionId: MediaStream }(host端使用) + participants: {} // 多Participant用户信息 Map: { participantId: { id, name, avatar, mediaState, status } }(host端使用) + }; + + // 监听器数组 + this.listeners = []; + } + + // 订阅状态变化 + subscribe(callback) { + this.listeners.push(callback); + return () => { + this.listeners = this.listeners.filter(cb => cb !== callback); + }; + } + + // 通知所有监听器 + notify(changes) { + this.listeners.forEach(cb => cb(this.state, changes)); + } + + // 初始化 + async init() { + // 初始化配置 + await this.setupConfig(); + // 加载用户设置 + this.loadUserSettings(); + // 获取本地摄像头视频流 + await this.getLocalStream(); + } + + // 加载用户设置 + loadUserSettings() { + const userSettings = localStorage.getItem('userSettings'); + if (userSettings) { + try { + const settings = JSON.parse(userSettings); + + // 更新本地用户信息 + if (settings.name || settings.avatar) { + this.state.session.localUser = { + ...this.state.session.localUser, + id: settings.userId || this.state.session.localUser.id, + name: settings.name || this.state.session.localUser.name, + avatar: settings.avatar || this.state.session.localUser.avatar + }; + + // 通知UI更新 + this.notify({ type: 'USER_SETTINGS_UPDATED', user: this.state.session.localUser }); + } + + // 恢复保存的分辨率设置 + if (settings.resolution) { + this._savedResolution = settings.resolution; + console.log(`已恢复分辨率设置: ${settings.resolution.width}x${settings.resolution.height}`); + } + } catch (error) { + console.error('Error loading user settings:', error); + } + } + } + async setupConfig() { + const res = await getServerConfig(); + this.useWebSocket = res.useWebSocket; + } + // 获取本地摄像头视频流 + async getLocalStream() { + try { + console.log('Requesting camera permission...'); + + // 检查浏览器是否支持getUserMedia + if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { + console.error('getUserMedia is not supported'); + throw new Error('getUserMedia is not supported'); + } + + // 请求摄像头权限并获取媒体流,启用回声消除 + // 使用保存的分辨率(如有),否则使用默认约束 + const videoConstraints = this._savedResolution + ? { + width: { ideal: this._savedResolution.width, max: this._savedResolution.width }, + height: { ideal: this._savedResolution.height, max: this._savedResolution.height }, + frameRate: { ideal: 30, max: 30 } + } + : MEDIA_CONSTRAINTS.video; + const stream = await navigator.mediaDevices.getUserMedia({ + video: videoConstraints, + audio: AUDIO_CONFIG + }); + + console.log('Stream obtained successfully:', stream); + console.log('Video tracks:', stream.getVideoTracks()); + console.log('Audio tracks:', stream.getAudioTracks()); + + this.state.localStream = stream; + this.state.session.localUser.mediaState.video = true; + this.state.session.localUser.mediaState.audio = true; + + console.log('Local stream stored, notifying UI...'); + + // 先通知视频流已获取 + this.notify({ type: 'LOCAL_STREAM_OBTAINED', stream }); + // 再通知媒体状态变化 + this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'video', value: true }); + this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'audio', value: true }); + + // 发送媒体状态到服务器 + this.emitMediaStateChange(); + + // 启动本地音频活动检测 + this.startActivityDetection(this.state.localStream, { isLocal: true }); + } catch (error) { + console.error('Error getting local stream:', error); + // 如果获取视频失败,保持视频关闭状态 + this.state.session.localUser.mediaState.video = false; + this.state.session.localUser.mediaState.audio = false; + + // 通知媒体状态变化 + this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'video', value: false }); + this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'audio', value: false }); + } + } + + // 更新本地媒体状态 + async updateLocalMedia(mediaType, value) { + + // 如果是开启视频,重新获取摄像头资源 + if (mediaType === 'video' && value) { + try { + // 只获取新的视频轨道,不干扰正在工作的音频 + const newVideoStream = await navigator.mediaDevices.getUserMedia(VIDEO_ONLY_CONSTRAINT); + const newVideoTrack = newVideoStream.getVideoTracks()[0]; + + if (!newVideoTrack) { + throw new Error('Failed to get video track'); + } + + // 更新本地流中的视频轨道(替换旧的已停止的轨道) + if (this.state.localStream) { + const oldVideoTracks = this.state.localStream.getVideoTracks(); + oldVideoTracks.forEach(track => { + track.stop(); + this.state.localStream.removeTrack(track); + }); + this.state.localStream.addTrack(newVideoTrack); + } else { + // 本地流不存在时(不应该发生),使用新流 + this.state.localStream = newVideoStream; + } + + // 更新WebRTC连接中的视频轨道 + if (this.renderstreaming) { + console.log('Updating video track in WebRTC connection'); + + if (this.role === 'host') { + // Host端:需要遍历所有participant的peer来替换视频轨道 + const participantIds = Object.keys(this.state.remoteStreams); + for (const participantId of participantIds) { + const transceivers = this.renderstreaming.getTransceivers(participantId); + if (!transceivers) continue; + + const videoTransceivers = transceivers.filter(t => + t.sender && t.sender.track && t.sender.track.kind === 'video' + ); + + if (videoTransceivers.length > 0) { + for (const transceiver of videoTransceivers) { + try { + await transceiver.sender.replaceTrack(newVideoTrack); + console.log(`Replaced video track for participant ${participantId}`); + } catch (error) { + console.error(`Error replacing video track for ${participantId}:`, error); + } + } + } else { + // 没有视频收发器,添加新的 + try { + this.renderstreaming.addTransceiver(newVideoTrack, { direction: 'sendonly' }, participantId); + console.log(`Added new video transceiver for participant ${participantId}`); + } catch (error) { + console.error(`Error adding video transceiver for ${participantId}:`, error); + } + } + + // 设置编解码器偏好 + setTimeout(() => { this.setCodecPreferences(participantId); }, 100); + setTimeout(() => { this.setVideoEncodingParameters(participantId); }, 200); + } + } else { + // Participant端:使用单一peer + const transceivers = this.renderstreaming.getTransceivers(); + if (transceivers) { + const videoTransceivers = transceivers.filter(t => + t.sender && t.sender.track && t.sender.track.kind === 'video' + ); + + if (videoTransceivers.length > 0) { + for (const transceiver of videoTransceivers) { + try { + await transceiver.sender.replaceTrack(newVideoTrack); + console.log('Successfully replaced video track'); + } catch (error) { + console.error('Error replacing video track:', error); + } + } + } else { + try { + this.renderstreaming.addTransceiver(newVideoTrack, { direction: 'sendonly' }); + console.log('Added new video transceiver'); + } catch (error) { + console.error('Error adding video transceiver:', error); + } + } + } + setTimeout(() => { this.setCodecPreferences(); }, 100); + setTimeout(() => { this.setVideoEncodingParameters(); }, 200); + } + } + + // 更新状态和通知UI + this.state.session.localUser.mediaState.video = true; + this.notify({ type: 'LOCAL_STREAM_OBTAINED', stream: this.state.localStream }); + this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'video', value: true }); + this.emitMediaStateChange(); + this.startActivityDetection(this.state.localStream, { isLocal: true }); + + } catch (error) { + console.error('Error reopening video:', error); + this.state.session.localUser.mediaState.video = false; + this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'video', value: false }); + } + } else { + // 直接更新媒体状态 + this.state.session.localUser.mediaState[mediaType] = value; + this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType, value }); + + // 发送媒体状态到服务器 + this.emitMediaStateChange(); + } + + + // 如果是关闭视频,释放摄像头资源 + if (mediaType === 'video' && !value && this.state.localStream) { + this.state.session.localUser.mediaState.video = false; + this.state.localStream.getTracks().forEach(track => { + if (track.kind === 'video') { + track.stop(); + } + }); + // 发送媒体状态到服务器 + this.emitMediaStateChange(); + + } + + // 如果是音频状态变化,控制本地音频轨道 + if (mediaType === 'audio' && this.state.localStream) { + this.state.session.localUser.mediaState.audio = value; + this.state.localStream.getTracks().forEach(track => { + if (track.kind === 'audio') { + track.enabled = value; + } + }); + // 发送媒体状态到服务器 + this.emitMediaStateChange(); + } + + // 通知UI更新用户列表 + this.notify({ type: 'USER_LIST_UPDATE', localUser: this.state.session.localUser, remoteUser: this.state.session.remoteUser }); + + } + + /** + * 创建信令和RTC实例 + * @async + * @param {string} connectionId - 连接ID + * @returns {Promise} + */ + async _createSignalingAndRTC(connectionId) { + this.connectionId = connectionId; // 获取连接ID + // 设置状态为连接中 + this.state.session.status = 'connecting'; + this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' }); + + // 确保本地流已经初始化 + if (!this.state.localStream) { + console.log('Local stream not available, waiting for initialization...'); + // 等待localStream初始化 + await new Promise((resolve) => { + const checkStream = () => { + if (this.state.localStream) { + resolve(); + } else { + setTimeout(checkStream, 100); + } + }; + checkStream(); + }); + } + + // 创建信令实例 + const signaling = this.useWebSocket ? new WebSocketSignaling() : new Signaling(); + const config = getRTCConfiguration(); // 获取RTC配置 + this.renderstreaming = new RenderStreaming(signaling, config); + } + + /** + * 设置WebRTC连接 + * @async + * @returns {Promise} + */ + async setUp(connectionId) { + await this._createSignalingAndRTC(connectionId); + this._registerCallbacks(); + await this._startConnection(connectionId); + } + + /** + * 注册所有WebRTC回调 + */ + _registerCallbacks() { + this.renderstreaming.onNewPeer = (participantId) => { + console.log(`New peer created for ${participantId}, adding local tracks`); + if (this.state.localStream) { + const tracks = this.state.localStream.getTracks(); + for (const track of tracks) { + this.renderstreaming.addTransceiver(track, { direction: 'sendonly' }, participantId); + } + this.setCodecPreferences(participantId); + this.setVideoEncodingParameters(participantId); + } + }; + + // 连接建立回调 + this.renderstreaming.onConnect = (connectionId, data) => { + // 保存角色信息(host/participant) + if (data && data.role) { + this.role = data.role; + // 更新localUser的isHost标志 + this.state.session.localUser.isHost = (this.role === 'host'); + // 保存自身的participantId,用于从participants-sync中过滤自身 + if (data.participantId) { + this.selfParticipantId = data.participantId; + } + console.log(`Connected as ${this.role}, participantId: ${this.selfParticipantId}`); + } + // 连接建立后,更新状态为ongoing + this.state.session.status = 'ongoing'; + this.notify({ type: 'CALL_STATUS_CHANGE', status: 'ongoing' }); + + // 参与者加入时默认静音:禁用音频轨道并更新状态 + if (this.role === 'participant') { + if (this.state.localStream) { + this.state.localStream.getAudioTracks().forEach(track => { + track.enabled = false; + }); + } + this.state.session.localUser.mediaState.audio = false; + this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'audio', value: false }); + console.log('Participant joined with audio muted by default'); + } + + // 连接建立后发送本地用户信息 + this.sendMessage('user-info', { + id: this.state.session.localUser.id, + name: this.state.session.localUser.name, + avatar: this.state.session.localUser.avatar + }); + + // 发送当前媒体状态,确保远端收到正确的初始状态 + this.emitMediaStateChange(); + + if (this.state.localStream) { + // const tracks = this.state.localStream.getTracks(); + // for (const track of tracks) { + // this.renderstreaming.addTransceiver(track, { direction: 'sendonly' }); + // } + // this.setCodecPreferences(); + this.showStatsMessage(); + } else { + console.error('Local stream is not available'); + showNotification('本地视频流不可用', 'error'); + } + }; + + // 连接断开回调(收到服务器的 disconnect 消息,通常是 host 离开导致房间关闭) + this.renderstreaming.onDisconnect = () => { + console.log('Received disconnect from server, host left or room closed'); + this.hangUp(); // 房间已关闭,挂断连接 + }; + + // SDP Answer 接收回调:重新设置编码参数以保障画质 + this.renderstreaming.onGotAnswer = (connectionId) => { + console.log('SDP Answer received, resetting encoding parameters for connectionId:', connectionId); + if (this.role === 'host') { + const allParticipantIds = Object.keys(this.state.remoteStreams || {}); + for (const pid of allParticipantIds) { + setTimeout(() => { this.setVideoEncodingParameters(pid); }, 50); + } + } else { + setTimeout(() => { this.setVideoEncodingParameters(); }, 50); + } + }; + + // participant加入回调(host收到,新participant加入房间) + this.renderstreaming.onParticipantJoined = (participantId) => { + console.log(`Participant joined: ${participantId}`); + if (!this.state.participants[participantId]) { + this.state.participants[participantId] = { + id: '', + name: '参与者', + avatar: '/images/p2.png', + mediaState: { audio: false, video: true, isSpeaking: false }, + status: 'online' + }; + } + this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants }); + this.broadcastParticipantsList(); + }; + + // participant离开回调(host收到,房间仍然存在) + this.renderstreaming.onParticipantLeft = (participantId) => { + console.log(`Participant left: ${participantId}, room still active`); + this.updateRemoteUserStatus('offline'); + this.updateRemoteUserNetworkQuality('no_signal'); + showNotification('对方已离开通话', 'warning'); + // 清理该 participant 的远端流 + if (this.state.remoteStreams[participantId]) { + this.state.remoteStreams[participantId].getTracks().forEach(track => track.stop()); + delete this.state.remoteStreams[participantId]; + } + if (this.state.remoteStream) { + this.state.remoteStream.getTracks().forEach(track => track.stop()); + this.state.remoteStream = null; + } + // 清理该 participant 的用户信息 + delete this.state.participants[participantId]; + // 通知UI更新,用participantId作为connectionId传给renderer + this.notify({ type: 'PARTICIPANT_LEFT', connectionId: participantId }); + this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants }); + this.broadcastParticipantsList(); + }; + + // 轨道事件回调 + this.renderstreaming.onTrackEvent = (data) => { + const direction = data.transceiver.direction; + if (direction == "sendrecv" || direction == "recvonly") { + // 使用participantId区分不同participant的流 + const trackParticipantId = data.participantId || this.connectionId; + const isHost = this.role === 'host'; + + let targetStream = null; + if (isHost) { + // Host端: 按 participantId 管理多路远端流 + if (!this.state.remoteStreams[trackParticipantId]) { + this.state.remoteStreams[trackParticipantId] = new MediaStream(); + } + targetStream = this.state.remoteStreams[trackParticipantId]; + } else { + // Participant端: 使用单一远端流 + if (this.state.remoteStream == null) { + this.state.remoteStream = new MediaStream(); + } + targetStream = this.state.remoteStream; + } + + // 检查是否已经有相同类型的轨道 + const existingTracks = targetStream.getTracks().filter(track => track.kind === data.track.kind); + existingTracks.forEach(track => { + targetStream.removeTrack(track); + console.log('Removed old track:', track.kind); + }); + + targetStream.addTrack(data.track); + console.log('Added new track:', data.track.kind, 'for participant:', trackParticipantId); + + // Host端兜底:确保participants中有该participant条目 + if (isHost && !this.state.participants[trackParticipantId]) { + this.state.participants[trackParticipantId] = { + id: '', + name: '参与者', + avatar: '/images/p2.png', + mediaState: { audio: false, video: true, isSpeaking: false }, + status: 'online' + }; + this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants }); + this.broadcastParticipantsList(); + } + + // 通知UI远程流已更新 + // 关键优化:如果是音频轨道先到达且流中尚无视频轨道, + // 延迟通知UI等待视频轨道到达,避免音频先触发的UI更新导致黑屏 + const notifyStreamUpdate = () => { + this.notify({ + type: 'REMOTE_STREAM_OBTAINED', + stream: targetStream, + connectionId: trackParticipantId, + isHost: isHost + }); + console.log('Notified UI about remote stream update'); + }; + + if (data.track.kind === 'audio' && targetStream.getVideoTracks().length === 0) { + // 音频先到,视频尚未到达:延迟200ms通知,给视频轨道到达的机会 + console.log('Audio track arrived first, delaying stream notification for video track...'); + setTimeout(() => { + const nowHasVideo = targetStream.getVideoTracks().length > 0; + console.log(`After delay, stream has video: ${nowHasVideo}`); + notifyStreamUpdate(); + }, 200); + } else { + // 视频轨道到达,或音频视频同时存在:立即通知 + notifyStreamUpdate(); + } + // 只有当收到远程流时才更新远程用户状态为在线 + if (this.state.session.remoteUser.status !== 'online') { + this.updateRemoteUserStatus('online'); + // 更新远程用户网络质量为好 + this.updateRemoteUserNetworkQuality('good'); + + this.sendMessage('user-info', { + id: this.state.session.localUser.id, + name: this.state.session.localUser.name, + avatar: this.state.session.localUser.avatar + }); + // 启动通话时长计时器(避免重复启动) + if (!this.durationInterval) { + this.durationInterval = setInterval(() => { + this.state.session.duration++; + this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration }); + }, 1000); + } + } + // 如果是音频轨道,启动远程音频活动检测 + if (data.track.kind === 'audio') { + this.startActivityDetection(this.state.remoteStream, { isLocal: false }); + } + } else if (direction == "sendonly") { + // 本地发送轨道,启动本地音频活动检测 + if (data.track.kind === 'audio') { + this.startActivityDetection(this.state.localStream, { isLocal: true }); + } + } + }; + + this.renderstreaming.onMessage = (data) => { + console.log('收到消息:', data); + if (data.type === 'chat-message') { + // 处理聊天 + // 添加到列表并更新UI + chatMessage.handleChatMessage(data.message); + // Host端:按participantId更新对应用户信息 + if (data.participantId && this.role === 'host' && this.state.participants[data.participantId]) { + this.state.participants[data.participantId].id = data.message.senderId; + if (data.message.senderName) { + this.state.participants[data.participantId].name = data.message.senderName; + } + if (data.message.senderAvatar) { + this.state.participants[data.participantId].avatar = data.message.senderAvatar; + } + this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants }); + this.broadcastParticipantsList(); + } + // Participant端:根据消息来源更新对应用户信息 + if (!this.role || this.role !== 'host') { + if (data.participantId && this.state.participants[data.participantId]) { + // 来自其他Participant的消息:更新participants中对应条目 + if (data.message.senderName) { + this.state.participants[data.participantId].name = data.message.senderName; + } + if (data.message.senderAvatar) { + this.state.participants[data.participantId].avatar = data.message.senderAvatar; + } + this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants }); + } else if (data.message && data.message.senderId !== this.state.session.localUser.id) { + // 来自Host的消息:更新remoteUser + this.state.session.remoteUser = { + ...this.state.session.remoteUser, + id: data.message.senderId, + name: data.message.senderName, + avatar: data.message.senderAvatar + }; + this.notify({ type: 'REMOTE_MEDIA_CHANGE', mediaState: this.state.session.remoteUser.mediaState }); + } + } + } else if (data.type === 'media-state-changed') { + // 处理媒体状态变化 + console.log('收到媒体状态变化:', data.data, 'from participant:', data.participantId); + if (this.role === 'host') { + // Host端:按participantId同步更新participants中对应participant的mediaState + if (data.participantId && this.state.participants[data.participantId]) { + this.state.participants[data.participantId].mediaState = { + ...this.state.participants[data.participantId].mediaState, + ...data.data + }; + } + // 更新远端媒体状态 + this.updateRemoteMedia(data.data, data.participantId); + // 通知UI更新participants + this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants }); + // Host端广播最新成员列表(含媒体状态)给所有Participant + this.broadcastParticipantsList(); + } else { + // Participant端:根据消息来源更新对应条目 + if (data.participantId && this.state.participants[data.participantId]) { + // 来自其他Participant的媒体状态变化:仅更新participants中对应条目 + // 不调用updateRemoteMedia,因为Participant端没有其他Participant的视频流 + this.state.participants[data.participantId].mediaState = { + ...this.state.participants[data.participantId].mediaState, + ...data.data + }; + this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants }); + } else if (!data.participantId) { + // 来自Host的媒体状态变化(无participantId): + // 更新participants中Host条目 + 更新remoteUser(Host的视频流是本端远端画面) + if (this.state.participants['host']) { + this.state.participants['host'].mediaState = { + ...this.state.participants['host'].mediaState, + ...data.data + }; + } + this.updateRemoteMedia(data.data, data.participantId); + this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants }); + } + } + } else if (data.type === 'user-info') { + // 处理用户信息更新 + console.log('收到用户信息:', data.data, 'from participant:', data.participantId); + if (data.data) { + if (data.participantId && this.role === 'host') { + // Host端:按participantId存储到participants Map + if (!this.state.participants[data.participantId]) { + this.state.participants[data.participantId] = { + id: '', + name: '参与者', + avatar: '/images/p2.png', + mediaState: { audio: false, video: true, isSpeaking: false }, + status: 'online' + }; + } + this.state.participants[data.participantId].id = data.data.id || ''; + this.state.participants[data.participantId].name = data.data.name || '参与者'; + this.state.participants[data.participantId].avatar = data.data.avatar || '/images/p2.png'; + this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants }); + this.broadcastParticipantsList(); + } else { + // Participant端:更新单一remoteUser(Host的信息) + this.state.session.remoteUser = { + ...this.state.session.remoteUser, + id: data.data.id || this.state.session.remoteUser.id, + name: data.data.name || this.state.session.remoteUser.name, + avatar: data.data.avatar || this.state.session.remoteUser.avatar + }; + this.notify({ type: 'REMOTE_MEDIA_CHANGE', mediaState: this.state.session.remoteUser.mediaState }); + } + } + } else if (data.type === 'participants-sync') { + // Participant端:接收Host广播的完整成员列表 + if (this.role !== 'host' && data.data) { + console.log('收到成员列表同步:', data.data); + // 过滤掉自身条目,避免在列表中重复显示(自身已作为localUser显示) + const filtered = {}; + for (const [pid, pInfo] of Object.entries(data.data)) { + if (pid !== this.selfParticipantId) { + filtered[pid] = pInfo; + } + } + this.state.participants = filtered; + this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants }); + + // 同步通话时长:仅首次同步,将Host的时长作为基准 + if (!this.durationSynced && typeof data.callDuration === 'number') { + this.state.session.duration = data.callDuration; + this.durationSynced = true; + // 如果计时器尚未启动(远程流还未到达),先启动计时器 + if (!this.durationInterval) { + this.durationInterval = setInterval(() => { + this.state.session.duration++; + this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration }); + }, 1000); + } + this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration }); + console.log(`通话时长已同步,当前时长: ${data.callDuration}秒`); + } + } + } + }; + } + + /** + * 启动WebRTC连接和检测 + * @async + * @param {string} connectionId - 连接ID + * @returns {Promise} + */ + async _startConnection(connectionId) { + // 启动WebRTC连接 + await this.renderstreaming.start(); + await this.renderstreaming.createConnection(connectionId); + + // 启动网络质量检测 + this.startNetworkQualityDetection(); + + // 启动本地音频活动检测 + this.startActivityDetection(this.state.localStream, { isLocal: true }); + //启动远端音频活动检测 + this.startActivityDetection(this.state.remoteStream, { isLocal: false }); + } + + /** + * 挂断WebRTC连接 + * Host挂断:房间删除,通知所有participants + * Participant挂断:仅自己离开,房间保留 + * @async + * @returns {Promise} + */ + async hangUp() { + this.clearStatsMessage(); // 清除统计信息 + this.stopNetworkQualityDetection(); // 停止网络质量检测 + // 停止通话时长计时器 + if (this.durationInterval) { + clearInterval(this.durationInterval); + this.durationInterval = null; + } + // 重置通话时长同步标志 + this.durationSynced = false; + + const isHost = this.role === 'host'; + console.log(`Disconnect peer on ${this.connectionId}. Role: ${this.role}`); + + // 删除连接并停止WebRTC + if (this.renderstreaming) { + try { + // 发送断开连接信令给服务器 + // 服务器会根据角色决定: + // - host断开:通知所有participants,删除房间 + // - participant断开:仅通知host,保留房间 + await this.renderstreaming.deleteConnection(); + await this.renderstreaming.stop(); + } catch (error) { + console.error('Error during hangUp:', error); + } + this.renderstreaming = null; + } + + // 更新远程用户状态为离线 + this.updateRemoteUserStatus('offline'); + this.updateRemoteUserNetworkQuality('no_signal'); + // 清理participants + this.state.participants = {}; + this.selfParticipantId = null; + this.connectionId = null; + this.role = null; + this.state.session.status = 'ended'; + this.notify({ type: 'CALL_ENDED', reason: isHost ? 'host_hangup' : 'participant_hangup' }); + } + + /** + * 发送消息 + * @param {string} type - 消息类型 + * @param {Object} data - 消息数据 + */ + sendMessage(type, data) { + if (this.renderstreaming) { + this.renderstreaming.sendMessage({ + type: type, + data: data + }); + } + } + + /** + * Host端广播完整成员列表给所有Participant + * 包含Host自身信息 + 所有Participant信息 + * Participant收到后可展示完整通话成员列表 + */ + broadcastParticipantsList() { + if (this.role !== 'host' || !this.renderstreaming) return; + + const memberList = {}; + + // 添加Host自身信息 + memberList['host'] = { + id: this.state.session.localUser.id, + name: this.state.session.localUser.name, + avatar: this.state.session.localUser.avatar, + mediaState: { ...this.state.session.localUser.mediaState }, + status: 'online', + role: 'host' + }; + + // 添加所有Participant信息 + for (const [pid, pInfo] of Object.entries(this.state.participants)) { + memberList[pid] = { + ...pInfo, + role: 'participant' + }; + } + + this.renderstreaming.sendMessage({ + type: 'participants-sync', + data: memberList, + callDuration: this.state.session.duration + }); + console.log('Broadcast participants list:', Object.keys(memberList)); + } + + /** + * 设置编解码器偏好 + * 优先选择 VP9/AV1(更高效的压缩),回退到 H264 High Profile + */ + setCodecPreferences(participantId) { + const capabilities = RTCRtpSender.getCapabilities('video'); + if (!capabilities || !capabilities.codecs || capabilities.codecs.length === 0) return; + const { codecs } = capabilities; + + // 构建多codec优先级列表(而非只选一个) + let selectedCodecs = []; + + const av1Codec = codecs.find(c => c.mimeType === 'video/AV1'); + const vp9Codec = codecs.find(c => c.mimeType === 'video/VP9'); + const h264HighCodec = codecs.find(c => + c.mimeType === 'video/H264' && + c.sdpFmtpLine && c.sdpFmtpLine.includes('profile-level-id=6400') + ); + const h264Codec = codecs.find(c => c.mimeType === 'video/H264'); + + if (av1Codec) selectedCodecs.push(av1Codec); + if (vp9Codec) selectedCodecs.push(vp9Codec); + if (h264HighCodec) selectedCodecs.push(h264HighCodec); + if (h264Codec && (!h264HighCodec || h264Codec !== h264HighCodec)) selectedCodecs.push(h264Codec); + + if (selectedCodecs.length === 0) return; + + if (this.renderstreaming) { + const transceivers = this.renderstreaming.getTransceivers(participantId); + if (transceivers && transceivers.length > 0) { + const videoTransceivers = transceivers.filter(t => { + if (t.sender && t.sender.track) { + return t.sender.track.kind === 'video'; + } + return t.mid !== null && t.receiver && t.receiver.track && t.receiver.track.kind === 'video'; + }); + if (videoTransceivers && videoTransceivers.length > 0) { + videoTransceivers.forEach(t => { + try { + t.setCodecPreferences(selectedCodecs); + } catch(e) { + console.error('Error setting codec preferences:', e); + } + }); + console.log(`Codec preferences set: ${selectedCodecs.map(c => c.mimeType).join(' > ')}`); + } + } + } + } + + /** + * 设置视频发送编码参数 + * 提升 maxBitrate 以改善远端视频画质 + * @param {string} [participantId] - 目标participant(host端使用) + */ + setVideoEncodingParameters(participantId) { + if (!this.renderstreaming) return; + + const transceivers = this.renderstreaming.getTransceivers(participantId); + if (!transceivers || transceivers.length === 0) return; + + const videoTransceivers = transceivers.filter(t => + t.sender && t.sender.track && t.sender.track.kind === 'video' + ); + + for (const transceiver of videoTransceivers) { + try { + const sender = transceiver.sender; + const params = sender.getParameters(); + + if (!params.encodings || params.encodings.length === 0) { + params.encodings = [{}]; + } + + // 根据实际采集分辨率动态设置maxBitrate + const videoTrack = sender.track; + const settings = videoTrack ? videoTrack.getSettings() : {}; + const height = settings.height || 1080; + + const bitrateMap = { + 270: 1000000, + 480: 1500000, + 720: 2500000, + 1080: 4000000, + 1440: 6000000 + }; + // 找到最接近的分辨率对应的比特率 + let maxBitrate = 4000000; + const heights = Object.keys(bitrateMap).map(Number).sort((a, b) => a - b); + for (const h of heights) { + if (height <= h) { + maxBitrate = bitrateMap[h]; + break; + } + maxBitrate = bitrateMap[h]; + } + + params.encodings[0].maxBitrate = maxBitrate; + params.encodings[0].scaleResolutionDownBy = 1.0; + params.encodings[0].xGoogleMinBitrate = Math.floor(maxBitrate * 0.5); + + // 优先保持分辨率,降低帧率来适应带宽 + // 'maintain-resolution' 在带宽不足时保持清晰度 + if (params.degradationPreference !== undefined) { + params.degradationPreference = 'maintain-resolution'; + } + + sender.setParameters(params); + console.log(`Set video encoding: maxBitrate=${maxBitrate / 1000000}Mbps, scaleResolutionDownBy=1.0, xGoogleMinBitrate=${Math.floor(maxBitrate * 0.5)}${participantId ? ` for ${participantId}` : ''}`); + } catch (error) { + console.error('Error setting video encoding parameters:', error); + } + } + } + + + /** + * 动态切换视频分辨率 + * 使用 MediaStreamTrack.applyConstraints() 在通话中实时调整 + * 同时根据分辨率调整编码比特率 + * @param {number} width - 目标宽度 + * @param {number} height - 目标高度 + */ + async changeResolution(width, height) { + if (!this.state.localStream) { + showNotification('本地视频流不可用', 'error'); + return; + } + + const videoTracks = this.state.localStream.getVideoTracks(); + if (videoTracks.length === 0) { + showNotification('视频轨道不可用', 'error'); + return; + } + + const track = videoTracks[0]; + const label = height >= 1440 ? '2K 1440p' : height >= 1080 ? '1080p 超清' : height >= 720 ? '720p 高清' : '480p 流畅'; + + try { + // 使用 applyConstraints 在不重新获取流的情况下调整分辨率 + await track.applyConstraints({ + width: { ideal: width, max: width }, + height: { ideal: height, max: height }, + frameRate: { ideal: 30, max: 30 } + }); + + console.log(`分辨率已切换为 ${width}x${height}`); + + // 根据分辨率调整编码比特率 + // 480p: ~1Mbps, 720p: ~2.5Mbps, 1080p: ~4Mbps, 2K: ~6Mbps + const bitrateMap = { + 270: 1000000, // 480p + 720: 2500000, // 720p + 1080: 4000000, // 1080p + 1440: 6000000 // 2K + }; + const maxBitrate = bitrateMap[height] || 2500000; + this._applyMaxBitrate(maxBitrate); + + // 保存当前分辨率设置到本地存储 + const userSettings = JSON.parse(localStorage.getItem('userSettings') || '{}'); + userSettings.resolution = { width, height }; + localStorage.setItem('userSettings', JSON.stringify(userSettings)); + + // 通知 UI 更新 + this.notify({ type: 'RESOLUTION_CHANGED', resolution: { width, height, label } }); + showNotification(`已切换为 ${label}`, 'success'); + } catch (error) { + console.error('切换分辨率失败:', error); + showNotification('切换分辨率失败,摄像头不支持该分辨率', 'error'); + } + } + + /** + * 对所有视频 sender 应用 maxBitrate + * @param {number} maxBitrate - 最大比特率(bps) + */ + _applyMaxBitrate(maxBitrate) { + if (!this.renderstreaming) return; + + const isHost = this.role === 'host'; + const participantIds = isHost ? Object.keys(this.state.participants) : [null]; + + for (const pid of participantIds) { + const transceivers = this.renderstreaming.getTransceivers(pid); + if (!transceivers) continue; + + const videoTransceivers = transceivers.filter(t => + t.sender && t.sender.track && t.sender.track.kind === 'video' + ); + + for (const transceiver of videoTransceivers) { + try { + const sender = transceiver.sender; + const params = sender.getParameters(); + if (!params.encodings || params.encodings.length === 0) { + params.encodings = [{}]; + } + params.encodings[0].maxBitrate = maxBitrate; + sender.setParameters(params); + console.log(`Updated maxBitrate to ${maxBitrate} for ${pid || 'self'}`); + } catch (error) { + console.error('Error updating maxBitrate:', error); + } + } + } + } + + // 更新远端媒体状态 (由 WebSocket 触发) + updateRemoteMedia(mediaState, participantId) { + this.state.session.remoteUser.mediaState = { + ...this.state.session.remoteUser.mediaState, + ...mediaState + }; + this.notify({ type: 'REMOTE_MEDIA_CHANGE', mediaState, participantId }); + // 通知UI更新用户列表 + this.notify({ type: 'USER_LIST_UPDATE', localUser: this.state.session.localUser, remoteUser: this.state.session.remoteUser }); + } + + // 更新远端用户状态 + updateRemoteUserStatus(status) { + this.state.session.remoteUser.status = status; + this.notify({ type: 'REMOTE_MEDIA_CHANGE', localUser: this.state.session.localUser, remoteUser: this.state.session.remoteUser }); + } + updateRemoteUserNetworkQuality(networkQuality) { + this.state.session.remoteUser.networkQuality = networkQuality; + this.notify({ type: 'REMOTE_MEDIA_CHANGE', localUser: this.state.session.localUser, remoteUser: this.state.session.remoteUser }); + } + // 结束通话(用户主动点击挂断按钮) + async endCall() { + console.log(`endCall called. Role: ${this.role}`); + // 调用 hangUp() 正确关闭 WebRTC 连接并发送断开信令 + // hangUp 内部会根据角色区分: + // - host: 通知所有participants,删除房间 + // - participant: 仅自己离开,房间保留 + await this.hangUp(); + } + + // 加入通话 + async joinCall(connectionId) { + this.state.session.status = 'connecting'; + this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' }); + showNotification(`正在加入通话 (${connectionId})`); + + // 初始化 + await this.init(); + + // 保存连接ID + this.connectionId = connectionId; + } + + // 创建通话 + async createCall() { + this.state.session.status = 'connecting'; + this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' }); + showNotification('正在创建通话...'); + + // 初始化 + await this.init(); + } + + // 真实网络质量检测 + async detectNetworkQuality() { + if (!this.renderstreaming) { + return; + } + + try { + const stats = await this.renderstreaming.getStats(); + if (!stats) { + return; + } + + let totalPacketsLost = 0; + let totalPacketsReceived = 0; + let inboundRTPCount = 0; + let jitter = 0; + let roundTripTime = 0; + + // 分析统计信息 + stats.forEach(report => { + if (report.type === 'inbound-rtp' && report.mediaType === 'video') { + inboundRTPCount++; + + // 计算丢包率 + if (report.packetsLost !== undefined && report.packetsReceived !== undefined) { + totalPacketsLost += report.packetsLost; + totalPacketsReceived += report.packetsReceived; + } + + // 获取抖动 + if (report.jitter !== undefined) { + jitter = Math.max(jitter, report.jitter); + } + + // 获取往返时间 + if (report.roundTripTime !== undefined) { + roundTripTime = Math.max(roundTripTime, report.roundTripTime); + } + } + }); + + // 计算网络质量指标 + let quality = 'excellent'; + + if (inboundRTPCount > 0) { + // 基于丢包率判断 + const packetLossRate = totalPacketsReceived > 0 ? (totalPacketsLost / (totalPacketsLost + totalPacketsReceived)) : 0; + + // 基于抖动判断 + const jitterMs = jitter * 1000; + + // 基于往返时间判断 + const rttMs = roundTripTime * 1000; + + // 综合评估网络质量 + if (packetLossRate > 0.05 || jitterMs > 100 || rttMs > 300) { + quality = 'poor'; + } else if (packetLossRate > 0.02 || jitterMs > 50 || rttMs > 150) { + quality = 'fair'; + } else if (packetLossRate > 0.01 || jitterMs > 30 || rttMs > 100) { + quality = 'good'; + } else { + quality = 'excellent'; + } + } else { + // 没有收到任何RTP包,设置为无信号状态 + quality = 'no_signal'; + } + + // 更新网络质量状态 + if (this.state.session.remoteUser.networkQuality !== quality) { + this.state.session.remoteUser.networkQuality = quality; + this.notify({ type: 'NETWORK_CHANGE', quality }); + } + + } catch (error) { + console.error('Error detecting network quality:', error); + } + } + // 音频活动检测 + startActivityDetection(stream, { isLocal = false } = {}) { + if (!stream) { + return; + } + + const audioTracks = stream.getAudioTracks(); + if (audioTracks.length === 0) { + return; + } + + try { + const { threshold, debounceTime, fftSize } = VAD_CONFIG; + + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + const source = audioContext.createMediaStreamSource(stream); + const analyser = audioContext.createAnalyser(); + analyser.fftSize = fftSize; + + source.connect(analyser); + + const dataArray = new Uint8Array(analyser.frequencyBinCount); + let isSpeaking = false; + let lastActivityTime = 0; + + const detectActivity = () => { + if (!stream || !this.renderstreaming) { + return; + } + + analyser.getByteTimeDomainData(dataArray); + + let sum = 0; + for (let i = 0; i < dataArray.length; i++) { + const amplitude = dataArray[i] - 128; + sum += amplitude * amplitude; + } + const rms = Math.sqrt(sum / dataArray.length); + const level = rms / 128; + + const currentTime = Date.now(); + if (level > threshold / 100) { + lastActivityTime = currentTime; + if (!isSpeaking) { + isSpeaking = true; + if (isLocal) { + this.state.session.localUser.mediaState.isSpeaking = true; + this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'isSpeaking', value: true }); + this.emitMediaStateChange(); + } else { + this.updateRemoteMedia({ isSpeaking: true }); + } + } + } else if (isSpeaking && currentTime - lastActivityTime > debounceTime) { + isSpeaking = false; + if (isLocal) { + this.state.session.localUser.mediaState.isSpeaking = false; + this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'isSpeaking', value: false }); + this.emitMediaStateChange(); + } else { + this.updateRemoteMedia({ isSpeaking: false }); + } + } + + if (this.state.session.status === 'ongoing') { + requestAnimationFrame(detectActivity); + } + }; + + detectActivity(); + + console.log(`${isLocal ? 'Local' : 'Remote'} activity detection started`); + + } catch (error) { + console.error(`Error starting ${isLocal ? 'local' : 'remote'} activity detection:`, error); + } + } + // 启动网络质量检测 + startNetworkQualityDetection() { + // 每3秒检测一次网络质量 + this.networkQualityInterval = setInterval(() => { + this.detectNetworkQuality(); + }, 3000); + } + + // 停止网络质量检测 + stopNetworkQualityDetection() { + if (this.networkQualityInterval) { + clearInterval(this.networkQualityInterval); + this.networkQualityInterval = null; + } + } + + + + // 发送媒体状态到服务器 + emitMediaStateChange() { + const payload = { + userId: this.state.session.localUser.id, + ...this.state.session.localUser.mediaState + }; + console.log('[WebSocket Emit] media-state-changed:', payload); + // 使用WebRTC发送媒体状态变化 + if (this.renderstreaming) { + this.renderstreaming.sendMessage({ + type: 'media-state-changed', + data: payload + }); + } + } + + // 显示统计信息 + async showStatsMessage() { + console.log('Showing stats message'); + + // 立即执行一次网络质量检测 + await this.detectNetworkQuality(); + + // 定期显示详细统计信息 + this.statsInterval = setInterval(async () => { + if (!this.renderstreaming) { + return; + } + + try { + const stats = await this.renderstreaming.getStats(); + if (!stats) { + return; + } + + let statsSummary = { + video: { + packetsLost: 0, + packetsReceived: 0, + bytesReceived: 0, + jitter: 0, + roundTripTime: 0, + fps: 0, + bitrate: 0 + }, + audio: { + packetsLost: 0, + packetsReceived: 0, + bytesReceived: 0, + jitter: 0 + } + }; + + // 分析统计信息 + stats.forEach(report => { + if (report.type === 'inbound-rtp') { + if (report.mediaType === 'video') { + statsSummary.video.packetsLost = report.packetsLost || 0; + statsSummary.video.packetsReceived = report.packetsReceived || 0; + statsSummary.video.bytesReceived = report.bytesReceived || 0; + statsSummary.video.jitter = report.jitter || 0; + statsSummary.video.roundTripTime = report.roundTripTime || 0; + statsSummary.video.fps = report.framesPerSecond || 0; + + // 计算视频比特率 (kbps) + if (report.bytesReceived && report.timestamp) { + const duration = report.timestamp / 1000; // 转换为秒 + statsSummary.video.bitrate = duration > 0 ? Math.round((report.bytesReceived * 8) / (duration * 1000)) : 0; + } + } else if (report.mediaType === 'audio') { + statsSummary.audio.packetsLost = report.packetsLost || 0; + statsSummary.audio.packetsReceived = report.packetsReceived || 0; + statsSummary.audio.bytesReceived = report.bytesReceived || 0; + statsSummary.audio.jitter = report.jitter || 0; + } + } + }); + + // 输出详细统计信息 + console.log('=== WebRTC Statistics ==='); + console.log(`Network Quality: ${this.state.session.remoteUser.networkQuality}`); + console.log('Video Stats:', { + 'Packets Lost': statsSummary.video.packetsLost, + 'Packets Received': statsSummary.video.packetsReceived, + 'Packet Loss Rate': statsSummary.video.packetsReceived > 0 ? + `${((statsSummary.video.packetsLost / (statsSummary.video.packetsLost + statsSummary.video.packetsReceived)) * 100).toFixed(2)}%` : '0%', + 'Jitter': `${(statsSummary.video.jitter * 1000).toFixed(2)}ms`, + 'Round Trip Time': `${(statsSummary.video.roundTripTime * 1000).toFixed(2)}ms`, + 'FPS': statsSummary.video.fps.toFixed(1), + 'Bitrate': `${statsSummary.video.bitrate}kbps` + }); + console.log('Audio Stats:', { + 'Packets Lost': statsSummary.audio.packetsLost, + 'Packets Received': statsSummary.audio.packetsReceived, + 'Packet Loss Rate': statsSummary.audio.packetsReceived > 0 ? + `${((statsSummary.audio.packetsLost / (statsSummary.audio.packetsLost + statsSummary.audio.packetsReceived)) * 100).toFixed(2)}%` : '0%', + 'Jitter': `${(statsSummary.audio.jitter * 1000).toFixed(2)}ms` + }); + console.log('========================'); + + } catch (error) { + console.error('Error showing stats message:', error); + } + }, 5000); // 每5秒更新一次统计信息 + } + + // 清除统计信息 + clearStatsMessage() { + console.log('Clearing stats message'); + + // 清理统计信息定时器 + if (this.statsInterval) { + clearInterval(this.statsInterval); + this.statsInterval = null; + } + } + + // Getters + getState() { return this.state; } + getLocalUser() { return this.state.session.localUser; } + getRemoteUser() { return this.state.session.remoteUser; } + + getConnectionId() { return this.connectionId; } + + getRenderStreaming() { return this.renderstreaming; } +} + +// 创建单例实例 +const store = new CallStateManager(); + +// 页面卸载前清理 +window.addEventListener('beforeunload', async () => { + if (!store.renderstreaming) + return; + await store.renderstreaming.stop(); // 停止WebRTC连接 +}, true); +export default store; diff --git a/client/public/onebyone/utils.js b/client/public/onebyone/utils.js new file mode 100644 index 0000000..213b170 --- /dev/null +++ b/client/public/onebyone/utils.js @@ -0,0 +1,94 @@ +/** + * 工具函数 + */ + +/** + * 格式化时间为 MM:SS 格式 + * @param {number} seconds - 秒数 + * @returns {string} 格式化后的时间字符串 + */ +export function formatTime(seconds) { + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; +} + +/** + * 格式化时间戳为 HH:MM 格式 + * @param {string} timestamp - ISO 8601 时间戳 + * @returns {string} 格式化后的时间字符串 + */ +export function formatTimestamp(timestamp) { + const date = new Date(timestamp); + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + return `${hours}:${minutes}`; +} + +/** + * 生成唯一ID + * @returns {string} 唯一ID + */ +export function generateId() { + return 'id-' + Math.random().toString(36).substr(2, 9); +} + +/** + * 显示通知 + * @param {string} message - 通知内容 + * @param {number} duration - 显示时长(毫秒) + */ +export function showNotification(message, duration = 3000) { + const notification = document.getElementById('notification'); + const notificationText = document.getElementById('notificationText'); + + if (notification && notificationText) { + notificationText.textContent = message; + notification.classList.remove('opacity-0', 'translate-y-[-20px]'); + notification.classList.add('opacity-100', 'translate-y-0'); + + setTimeout(() => { + notification.classList.remove('opacity-100', 'translate-y-0'); + notification.classList.add('opacity-0', 'translate-y-[-20px]'); + }, duration); + } +} + +/** + * 切换元素的显示/隐藏 + * @param {HTMLElement} element - DOM元素 + * @param {boolean} show - 是否显示 + */ +export function toggleElement(element, show) { + if (element) { + if (show) { + element.classList.remove('hidden'); + } else { + element.classList.add('hidden'); + } + } +} + +/** + * 切换按钮状态 + * @param {HTMLElement} button - 按钮元素 + * @param {boolean} active - 是否激活 + */ +export function toggleButtonState(button, active) { + if (button) { + button.dataset.active = active; + + const defaultIcon = button.querySelector('[data-icon="default"]'); + const activeIcon = button.querySelector('[data-icon="active"]'); + + if (defaultIcon && activeIcon) { + if (active) { + defaultIcon.classList.add('hidden'); + activeIcon.classList.remove('hidden'); + } else { + defaultIcon.classList.remove('hidden'); + activeIcon.classList.add('hidden'); + } + } + } +} diff --git a/client/public/receiver/css/style.css b/client/public/receiver/css/style.css new file mode 100644 index 0000000..4248609 --- /dev/null +++ b/client/public/receiver/css/style.css @@ -0,0 +1,43 @@ +body { + margin: 0px; +} + +#player { + position: relative; + top: 0; + right: 0; + bottom: 0; + left: 0; + align-items: center; + justify-content: center; + display: flex; + background-color: #323232; +} + +#player:before { + content: ""; + display: block; + padding-top: 66%; +} + +#playButton { + width: 15%; + max-width: 200px; + cursor: pointer; +} + +#Video { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +#fullscreenButton { + position: absolute; + top: 25px; + right: 25px; + width: 32px; + height: 32px; +} \ No newline at end of file diff --git a/client/public/receiver/index.html b/client/public/receiver/index.html new file mode 100644 index 0000000..56b281b --- /dev/null +++ b/client/public/receiver/index.html @@ -0,0 +1,53 @@ + + + + + + + + + Receiver Sample + + + + +
    +

    Receiver Sample

    + + + +
    + +
    + Codec preferences: + +
    + +
    + Lock Cursor to Player: + +
    + +

    + For more information about sample, see + Broadcast sample document page. +

    + +
    + +
    + View source on GitHub +
    +
    + + + + + + + + + diff --git a/client/public/receiver/js/main.js b/client/public/receiver/js/main.js new file mode 100644 index 0000000..ba41f78 --- /dev/null +++ b/client/public/receiver/js/main.js @@ -0,0 +1,186 @@ +import { getServerConfig, getRTCConfiguration } from "../../js/config.js"; +import { createDisplayStringArray } from "../../js/stats.js"; +import { VideoPlayer } from "../../js/videoplayer.js"; +import { RenderStreaming } from "../../module/renderstreaming.js"; +import { Signaling, WebSocketSignaling } from "../../module/signaling.js"; + +/** @type {Element} */ +let playButton; +/** @type {RenderStreaming} */ +let renderstreaming; +/** @type {boolean} */ +let useWebSocket; + +const codecPreferences = document.getElementById('codecPreferences'); +const supportsSetCodecPreferences = window.RTCRtpTransceiver && + 'setCodecPreferences' in window.RTCRtpTransceiver.prototype; +const messageDiv = document.getElementById('message'); +messageDiv.style.display = 'none'; + +const playerDiv = document.getElementById('player'); +const lockMouseCheck = document.getElementById('lockMouseCheck'); +const videoPlayer = new VideoPlayer(); + +setup(); + +window.document.oncontextmenu = function () { + return false; // cancel default menu +}; + +window.addEventListener('resize', function () { + videoPlayer.resizeVideo(); +}, true); + +window.addEventListener('beforeunload', async () => { + if(!renderstreaming) + return; + await renderstreaming.stop(); +}, true); + +async function setup() { + const res = await getServerConfig(); + useWebSocket = res.useWebSocket; + showWarningIfNeeded(res.startupMode); + showCodecSelect(); + showPlayButton(); +} + +function showWarningIfNeeded(startupMode) { + const warningDiv = document.getElementById("warning"); + if (startupMode == "private") { + warningDiv.innerHTML = "

    Warning

    This sample is not working on Private Mode."; + warningDiv.hidden = false; + } +} + +function showPlayButton() { + if (!document.getElementById('playButton')) { + const elementPlayButton = document.createElement('img'); + elementPlayButton.id = 'playButton'; + elementPlayButton.src = '../../images/Play.png'; + elementPlayButton.alt = 'Start Streaming'; + playButton = document.getElementById('player').appendChild(elementPlayButton); + playButton.addEventListener('click', onClickPlayButton); + } +} + +function onClickPlayButton() { + playButton.style.display = 'none'; + + // add video player + videoPlayer.createPlayer(playerDiv, lockMouseCheck); + setupRenderStreaming(); +} + +async function setupRenderStreaming() { + codecPreferences.disabled = true; + + const signaling = useWebSocket ? new WebSocketSignaling() : new Signaling(); + const config = getRTCConfiguration(); + renderstreaming = new RenderStreaming(signaling, config); + renderstreaming.onConnect = onConnect; + renderstreaming.onDisconnect = onDisconnect; + renderstreaming.onTrackEvent = (data) => videoPlayer.addTrack(data.track); + renderstreaming.onGotOffer = setCodecPreferences; + + await renderstreaming.start(); + await renderstreaming.createConnection(); +} + +function onConnect() { + const channel = renderstreaming.createDataChannel("input"); + videoPlayer.setupInput(channel); + showStatsMessage(); +} + +async function onDisconnect(connectionId) { + clearStatsMessage(); + messageDiv.style.display = 'block'; + messageDiv.innerText = `Disconnect peer on ${connectionId}.`; + + await renderstreaming.stop(); + renderstreaming = null; + videoPlayer.deletePlayer(); + if (supportsSetCodecPreferences) { + codecPreferences.disabled = false; + } + showPlayButton(); +} + +function setCodecPreferences() { + /** @type {RTCRtpCodecCapability[] | null} */ + let selectedCodecs = null; + if (supportsSetCodecPreferences) { + const preferredCodec = codecPreferences.options[codecPreferences.selectedIndex]; + if (preferredCodec.value !== '') { + const [mimeType, sdpFmtpLine] = preferredCodec.value.split(' '); + const { codecs } = RTCRtpSender.getCapabilities('video'); + const selectedCodecIndex = codecs.findIndex(c => c.mimeType === mimeType && c.sdpFmtpLine === sdpFmtpLine); + const selectCodec = codecs[selectedCodecIndex]; + selectedCodecs = [selectCodec]; + } + } + + if (selectedCodecs == null) { + return; + } + const transceivers = renderstreaming.getTransceivers().filter(t => t.receiver.track.kind == "video"); + if (transceivers && transceivers.length > 0) { + transceivers.forEach(t => t.setCodecPreferences(selectedCodecs)); + } +} + +function showCodecSelect() { + if (!supportsSetCodecPreferences) { + messageDiv.style.display = 'block'; + messageDiv.innerHTML = `Current Browser does not support RTCRtpTransceiver.setCodecPreferences.`; + return; + } + + const codecs = RTCRtpSender.getCapabilities('video').codecs; + codecs.forEach(codec => { + if (['video/red', 'video/ulpfec', 'video/rtx'].includes(codec.mimeType)) { + return; + } + const option = document.createElement('option'); + option.value = (codec.mimeType + ' ' + (codec.sdpFmtpLine || '')).trim(); + option.innerText = option.value; + codecPreferences.appendChild(option); + }); + codecPreferences.disabled = false; +} + +/** @type {RTCStatsReport} */ +let lastStats; +/** @type {number} */ +let intervalId; + +function showStatsMessage() { + intervalId = setInterval(async () => { + if (renderstreaming == null) { + return; + } + + const stats = await renderstreaming.getStats(); + if (stats == null) { + return; + } + + const array = createDisplayStringArray(stats, lastStats); + if (array.length) { + messageDiv.style.display = 'block'; + messageDiv.innerHTML = array.join('
    '); + } + lastStats = stats; + }, 1000); +} + +function clearStatsMessage() { + if (intervalId) { + clearInterval(intervalId); + } + lastStats = null; + intervalId = null; + messageDiv.style.display = 'none'; + messageDiv.innerHTML = ''; +} \ No newline at end of file diff --git a/client/public/videoplayer/css/style.css b/client/public/videoplayer/css/style.css new file mode 100644 index 0000000..0efec66 --- /dev/null +++ b/client/public/videoplayer/css/style.css @@ -0,0 +1,103 @@ + +body { + margin: 0px; +} +button#muteButton { + margin: 5px 0; + width: auto; +} +#player { + position: relative; + top: 0; + right: 0; + bottom: 0; + left: 0; + align-items: center; + justify-content: center; + display: flex; + background-color: #323232; +} + +#player:before { + content: ""; + display: block; + padding-top: 66%; +} + +#playButton { + width: 15%; + max-width: 200px; + cursor: pointer; +} + +#Video { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +#VideoThumbnail { + position: absolute; + top: 0; + left: 0; + width: 30%; + height: 30%; +} + +#greenButton { + position: absolute; + bottom: 10px; + left: 10px; + width: 160px; + background-color: #4CAF50; + /* Green */ + border: none; + color: white; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; +} + +#blueButton { + position: absolute; + bottom: 10px; + left: 180px; + width: 160px; + background-color: #447FAF; + /* Blue */ + border: none; + color: white; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; +} + +#orangeButton { + position: absolute; + bottom: 10px; + left: 350px; + width: 160px; + background-color: #FF7700; + /* Blue */ + border: none; + color: white; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; +} + +#fullscreenButton { + position: absolute; + top: 25px; + right: 25px; + width: 32px; + height: 32px; +} diff --git a/client/public/videoplayer/images/FullScreen.png b/client/public/videoplayer/images/FullScreen.png new file mode 100644 index 0000000..ba386e6 Binary files /dev/null and b/client/public/videoplayer/images/FullScreen.png differ diff --git a/client/public/videoplayer/images/Play.png b/client/public/videoplayer/images/Play.png new file mode 100644 index 0000000..9d8dbd1 Binary files /dev/null and b/client/public/videoplayer/images/Play.png differ diff --git a/client/public/videoplayer/index.html b/client/public/videoplayer/index.html new file mode 100644 index 0000000..58dfc6b --- /dev/null +++ b/client/public/videoplayer/index.html @@ -0,0 +1,37 @@ + + + + + + + + + VideoPlayer Sample + + + + +
    +

    VideoPlayer Sample

    + + + +
    + +

    For more information about WebBrowserInput sample, see WebBrowserInput + sample document page.

    + +
    + View source on GitHub +
    +
    + + + + + + + + + diff --git a/client/public/videoplayer/js/gamepadEvents.js b/client/public/videoplayer/js/gamepadEvents.js new file mode 100644 index 0000000..7b164ff --- /dev/null +++ b/client/public/videoplayer/js/gamepadEvents.js @@ -0,0 +1,146 @@ +import * as Logger from "../../module/logger.js"; + +const _e = 0.09; +const _gameloopInterval = 16.67; //in milliseconds, 60 times a second +var gameloop = null; +var gamepadsPreviousButtonsStates = {}; +var gamepadsPreviousAxesStates = {}; +var gamepadsConnectedTimeStamp = {}; +const _axisOffset = 100; +const _axisMultiplier = 1; +const _axisYInverted = -1; + +class GamepadButtonEvent extends Event { + constructor() { + super(...arguments); + this.index = arguments[1].index; + this.id = arguments[1].id; + this.value = arguments[1].value; + } +} + +class GamepadAxisEvent extends Event { + constructor() { + super(...arguments); + this.index = arguments[1].index; + this.x = arguments[1].x; + this.y = arguments[1].y; + this.id = arguments[1].id; + } +} + +function storePreviousState(gamepad) { + gamepadsPreviousButtonsStates[gamepad.index] = {}; + gamepad.buttons.forEach(function (button, index) { + gamepadsPreviousButtonsStates[gamepad.index][index] = { value: button.value, pressed: button.pressed }; + }); + + gamepadsPreviousAxesStates[gamepad.index] = [gamepad.axes.length]; + for (var index = 0; index < gamepad.axes.length; index++) + gamepadsPreviousAxesStates[gamepad.index][index] = gamepad.axes[index]; +} + +function checkAxes(gamepad, previousGamePad) { + for (var i = 0; i < gamepad.axes.length; i += 2) { + var absX = Math.abs(gamepad.axes[i]); + var absY = Math.abs(gamepad.axes[i + 1]); + var event = null; + if ((absX > _e) || + (absY > _e)) { + + event = new GamepadAxisEvent('gamepadAxis', { id: gamepadsConnectedTimeStamp[gamepad.index], index: i / 2 + _axisOffset, x: gamepad.axes[i] * _axisMultiplier, y: gamepad.axes[i + 1] * _axisMultiplier * _axisYInverted }); + document.dispatchEvent(event); + } + else { + var previousAbsX = Math.abs(previousGamePad[i]); + var previousAbsY = Math.abs(previousGamePad[i + 1]); + + //have to send if previously was moved + if ((previousAbsX > _e) || + (previousAbsY > _e)) { + event = new GamepadAxisEvent('gamepadAxis', { id: gamepadsConnectedTimeStamp[gamepad.index], index: i / 2 + _axisOffset, x: 0.0, y: 0.0 }); + document.dispatchEvent(event); + } + } + } +} + +function gameLoop() { + Object.keys(gamepadsPreviousAxesStates).forEach(function (gamepadIndex) { + var gamepad = navigator.webkitGetGamepads ? navigator.webkitGetGamepads()[gamepadIndex] : navigator.getGamepads()[gamepadIndex]; + var previousButtons = gamepadsPreviousButtonsStates[gamepadIndex]; + gamepad.buttons.forEach(function (button, index) { + var buttonStatus = navigator.webkitGetGamepads ? button == 1 : (button.value > 0 || button.pressed == true); + var previousButtonStatus = navigator.webkitGetGamepads ? previousButtons[index].value == 1 : (previousButtons[index].value > 0 || previousButtons[index].pressed == true); + var event; + if (buttonStatus != previousButtonStatus) { + if (buttonStatus) { + event = new GamepadButtonEvent('gamepadButtonDown', { id: gamepadsConnectedTimeStamp[gamepad.index], index: index, value: button.value }); + } + else { + event = new GamepadButtonEvent('gamepadButtonUp', { id: gamepadsConnectedTimeStamp[gamepad.index], index: index, value: 0 }); + } + document.dispatchEvent(event); + } + else if (buttonStatus) { + event = new GamepadButtonEvent('gamepadButtonPressed', { id: gamepadsConnectedTimeStamp[gamepad.index], index: index, value: button.value }); + document.dispatchEvent(event); + } + + }); + checkAxes(gamepad, gamepadsPreviousAxesStates[gamepadIndex]); + storePreviousState(gamepad); + }); +} + +function getCookie(cname) { + var name = cname + "="; + var decodedCookie = decodeURIComponent(document.cookie); + var ca = decodedCookie.split(';'); + for (var i = 0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) == ' ') { + c = c.substring(1); + } + if (c.indexOf(name) == 0) { + return c.substring(name.length, c.length); + } + } + return ""; +} + +export function gamepadHandler(event, connecting) { + var gamepad = event.gamepad; + + var key = gamepad.id.replace(/\s/g, ''); + var cookieTimeStamp = getCookie(key); + + if (connecting) { + storePreviousState(gamepad); + if (Object.keys(gamepadsPreviousAxesStates).length == 1) { + gameloop = setInterval(gameLoop, _gameloopInterval); + } + + //try to find the timestamp + //need to strip the : from the id + + if (cookieTimeStamp == "") { + document.cookie = key + "=" + gamepad.timestamp; + gamepadsConnectedTimeStamp[gamepad.index] = gamepad.timestamp; + } + else { + gamepadsConnectedTimeStamp[gamepad.index] = cookieTimeStamp; + } + + Logger.log("connected: " + gamepadsConnectedTimeStamp[gamepad.index]); + + } else { + delete gamepadsPreviousAxesStates[gamepad.index]; + delete gamepadsPreviousButtonsStates[gamepad.index]; + if (Object.keys(gamepadsPreviousAxesStates).length == 0) { + clearInterval(gameloop); + gameloop = null; + } + Logger.log("disconnected: " + gamepad.id); + } +} diff --git a/client/public/videoplayer/js/main.js b/client/public/videoplayer/js/main.js new file mode 100644 index 0000000..4eb8987 --- /dev/null +++ b/client/public/videoplayer/js/main.js @@ -0,0 +1,156 @@ +import { VideoPlayer } from "./video-player.js"; +import { registerGamepadEvents, registerKeyboardEvents, registerMouseEvents, sendClickEvent } from "./register-events.js"; +import { getServerConfig } from "../../js/config.js"; + +setup(); + +let playButton; +let videoPlayer; +let useWebSocket; + +window.document.oncontextmenu = function () { + return false; // cancel default menu +}; + +window.addEventListener('resize', function () { + videoPlayer.resizeVideo(); +}, true); + +window.addEventListener('beforeunload', async () => { + await videoPlayer.stop(); +}, true); + +async function setup() { + const res = await getServerConfig(); + useWebSocket = res.useWebSocket; + showWarningIfNeeded(res.startupMode); + showPlayButton(); +} + +function showWarningIfNeeded(startupMode) { + const warningDiv = document.getElementById("warning"); + if (startupMode == "private") { + warningDiv.innerHTML = "

    Warning

    This sample is not working on Private Mode."; + warningDiv.hidden = false; + } +} + +function showPlayButton() { + if (!document.getElementById('playButton')) { + let elementPlayButton = document.createElement('img'); + elementPlayButton.id = 'playButton'; + elementPlayButton.src = 'images/Play.png'; + elementPlayButton.alt = 'Start Streaming'; + playButton = document.getElementById('player').appendChild(elementPlayButton); + playButton.addEventListener('click', onClickPlayButton); + } +} + +function onClickPlayButton() { + + playButton.style.display = 'none'; + + const playerDiv = document.getElementById('player'); + + // add video player + const elementVideo = document.createElement('video'); + elementVideo.id = 'Video'; + elementVideo.style.touchAction = 'none'; + playerDiv.appendChild(elementVideo); + + // add video thumbnail + const elementVideoThumb = document.createElement('video'); + elementVideoThumb.id = 'VideoThumbnail'; + elementVideoThumb.style.touchAction = 'none'; + playerDiv.appendChild(elementVideoThumb); + + setupVideoPlayer([elementVideo, elementVideoThumb]).then(value => videoPlayer = value); + + // add blue button + const elementBlueButton = document.createElement('button'); + elementBlueButton.id = "blueButton"; + elementBlueButton.innerHTML = "Light on"; + playerDiv.appendChild(elementBlueButton); + elementBlueButton.addEventListener("click", function () { + sendClickEvent(videoPlayer, 1); + }); + + // add green button + const elementGreenButton = document.createElement('button'); + elementGreenButton.id = "greenButton"; + elementGreenButton.innerHTML = "Light off"; + playerDiv.appendChild(elementGreenButton); + elementGreenButton.addEventListener("click", function () { + sendClickEvent(videoPlayer, 2); + }); + + // add orange button + const elementOrangeButton = document.createElement('button'); + elementOrangeButton.id = "orangeButton"; + elementOrangeButton.innerHTML = "Play audio"; + playerDiv.appendChild(elementOrangeButton); + elementOrangeButton.addEventListener("click", function () { + sendClickEvent(videoPlayer, 3); + }); + + // add fullscreen button + const elementFullscreenButton = document.createElement('img'); + elementFullscreenButton.id = 'fullscreenButton'; + elementFullscreenButton.src = 'images/FullScreen.png'; + playerDiv.appendChild(elementFullscreenButton); + elementFullscreenButton.addEventListener("click", function () { + if (!document.fullscreenElement || !document.webkitFullscreenElement) { + if (document.documentElement.requestFullscreen) { + document.documentElement.requestFullscreen(); + } + else if (document.documentElement.webkitRequestFullscreen) { + document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } else { + if (playerDiv.style.position == "absolute") { + playerDiv.style.position = "relative"; + } else { + playerDiv.style.position = "absolute"; + } + } + } + }); + document.addEventListener('webkitfullscreenchange', onFullscreenChange); + document.addEventListener('fullscreenchange', onFullscreenChange); + + function onFullscreenChange() { + if (document.webkitFullscreenElement || document.fullscreenElement) { + playerDiv.style.position = "absolute"; + elementFullscreenButton.style.display = 'none'; + } + else { + playerDiv.style.position = "relative"; + elementFullscreenButton.style.display = 'block'; + } + } +} + +async function setupVideoPlayer(elements) { + const videoPlayer = new VideoPlayer(elements); + await videoPlayer.setupConnection(useWebSocket); + + videoPlayer.ondisconnect = onDisconnect; + registerGamepadEvents(videoPlayer); + registerKeyboardEvents(videoPlayer); + registerMouseEvents(videoPlayer, elements[0]); + + return videoPlayer; +} + +function onDisconnect() { + const playerDiv = document.getElementById('player'); + clearChildren(playerDiv); + videoPlayer.stop(); + videoPlayer = null; + showPlayButton(); +} + +function clearChildren(element) { + while (element.firstChild) { + element.removeChild(element.firstChild); + } +} diff --git a/client/public/videoplayer/js/register-events.js b/client/public/videoplayer/js/register-events.js new file mode 100644 index 0000000..4ef129e --- /dev/null +++ b/client/public/videoplayer/js/register-events.js @@ -0,0 +1,307 @@ +import { gamepadHandler } from "./gamepadEvents.js"; +import * as Logger from "../../module/logger.js"; +import { Keymap } from "../../module/keymap.js"; + +const InputEvent = { + Keyboard: 0, + Mouse: 1, + MouseWheel: 2, + Touch: 3, + ButtonClick: 4, + Gamepad: 5 +}; + +const KeyboardEventType = { + Up: 0, + Down: 1 +}; + +const GamepadEventType = { + ButtonUp: 0, + ButtonDown: 1, + ButtonPressed: 2, + Axis: 3 +}; + +const PointerPhase = { + None: 0, + Began: 1, + Moved: 2, + Ended: 3, + Canceled: 4, + Stationary: 5 +}; + +let sendGamepadButtonDown = undefined; +let sendGamepadButtonUp = undefined; +let sendGamepadButtonPressed; +let gamepadAxisChange = undefined; +let gamepadConnected = undefined; +let gamepadDisconnected = undefined; + +export function registerGamepadEvents(videoPlayer) { + + const _videoPlayer = videoPlayer; + + sendGamepadButtonDown = (e) => { + Logger.log("gamepad id: " + e.id + " button index: " + e.index + " value " + e.value + " down"); + let data = new DataView(new ArrayBuffer(19)); + data.setUint8(0, InputEvent.Gamepad); + data.setUint8(1, GamepadEventType.ButtonDown); + data.setUint8(2, e.index); + data.setFloat64(3, e.value, true); + + _videoPlayer && _videoPlayer.sendMsg(data.buffer); + }; + + sendGamepadButtonUp = (e) => { + Logger.log("gamepad id: " + e.id + " button index: " + e.index + " value " + e.value + " up"); + let data = new DataView(new ArrayBuffer(19)); + data.setUint8(0, InputEvent.Gamepad); + data.setUint8(1, GamepadEventType.ButtonUp); + data.setUint8(2, e.index); + data.setFloat64(3, e.value, true); + + _videoPlayer && _videoPlayer.sendMsg(data.buffer); + }; + + sendGamepadButtonPressed = (e) => { + Logger.log("gamepad id: " + e.id + " button index: " + e.index + " value " + e.value + " pressed"); + let data = new DataView(new ArrayBuffer(19)); + data.setUint8(0, InputEvent.Gamepad); + data.setUint8(1, GamepadEventType.ButtonPressed); + data.setUint8(2, e.index); + data.setFloat64(3, e.value, true); + + _videoPlayer && _videoPlayer.sendMsg(data.buffer); + }; + + gamepadAxisChange = (e) => { + Logger.log("gamepad id: " + e.id + " axis: " + e.index + " value " + e.value + " x:" + e.x + " y:" + e.y); + let data = new DataView(new ArrayBuffer(27)); + data.setUint8(0, InputEvent.Gamepad); + data.setUint8(1, GamepadEventType.Axis); + data.setUint8(2, e.index); + data.setFloat64(3, e.x, true); + data.setFloat64(11, e.y, true); + _videoPlayer && _videoPlayer.sendMsg(data.buffer); + }; + + gamepadConnected = (e) => { gamepadHandler(e, true); }; + gamepadDisconnected = (e) => { gamepadHandler(e, false); }; + + + document.addEventListener("gamepadButtonDown", sendGamepadButtonDown, false); + document.addEventListener("gamepadButtonUp", sendGamepadButtonUp, false); + document.addEventListener("gamepadButtonPressed", sendGamepadButtonPressed, false); + document.addEventListener("gamepadAxis", gamepadAxisChange, false); + + window.addEventListener("gamepadconnected", gamepadConnected, false); + window.addEventListener("gamepaddisconnected", gamepadDisconnected, false); +} + +export function unregisterGamepadEvents() { + + document.removeEventListener("gamepadButtonDown", sendGamepadButtonDown, false); + document.removeEventListener("gamepadButtonUp", sendGamepadButtonUp, false); + document.removeEventListener("gamepadButtonPressed", sendGamepadButtonPressed, false); + document.removeEventListener("gamepadAxis", gamepadAxisChange, false); + + window.removeEventListener("gamepadconnected", gamepadConnected, false); + window.removeEventListener("gamepaddisconnected", gamepadDisconnected, false); + +} + + +let sendKeyUp = undefined; +let sendKeyDown = undefined; + + +export function registerKeyboardEvents(videoPlayer) { + + const _videoPlayer = videoPlayer; + + function sendKey(e, type) { + const key = Keymap[e.code]; + const character = e.key.length === 1 ? e.key.charCodeAt(0) : 0; + Logger.log("key down " + key + ", repeat = " + e.repeat + ", character = " + character); + _videoPlayer && _videoPlayer.sendMsg(new Uint8Array([InputEvent.Keyboard, type, e.repeat, key, character]).buffer); + } + + + sendKeyUp = (e) => { + sendKey(e, KeyboardEventType.Up); + }; + + sendKeyDown = (e) => { + sendKey(e, KeyboardEventType.Down); + }; + + document.addEventListener('keyup', sendKeyUp, false); + document.addEventListener('keydown', sendKeyDown, false); +} + + +export function unregisterKeyboardEvents() { + + //Stop listening to keyboard events + document.removeEventListener('keyup', sendKeyUp, false); + document.removeEventListener('keydown', sendKeyDown, false); +} + + +let sendMouse = undefined; +let sendMouseWheel = undefined; +let sendTouchEnd = undefined; +let sendTouchStart = undefined; +let sendTouchCancel = undefined; +let sendTouchMove = undefined; + + +export function registerMouseEvents(videoPlayer, playerElement) { + + const _videoPlayer = videoPlayer; + + function sendTouch(e, phase) { + const changedTouches = Array.from(e.changedTouches); + const touches = Array.from(e.touches); + const phrases = []; + + for (let i = 0; i < changedTouches.length; i++) { + if (touches.find(function (t) { + return t.identifier === changedTouches[i].identifier; + }) === undefined) { + touches.push(changedTouches[i]); + } + } + + for (let i = 0; i < touches.length; i++) { + touches[i].identifier; + phrases[i] = changedTouches.find( + function (e) { + return e.identifier === touches[i].identifier; + }) === undefined ? PointerPhase.Stationary : phase; + } + + Logger.log("touch phase:" + phase + " length:" + changedTouches.length + " pageX" + changedTouches[0].pageX + ", pageX: " + changedTouches[0].pageY + ", force:" + changedTouches[0].force); + + let data = new DataView(new ArrayBuffer(2 + 13 * touches.length)); + data.setUint8(0, InputEvent.Touch); + data.setUint8(1, touches.length); + let byteOffset = 2; + for (let i = 0; i < touches.length; i++) { + + const scale = _videoPlayer.videoScale; + const originX = _videoPlayer.videoOriginX; + const originY = _videoPlayer.videoOriginY; + + const x = (touches[i].pageX - originX) / scale; + // According to Unity Coordinate system + // const y = (touches[i].pageX - originY) / scale; + const y = _videoPlayer.videoHeight - (touches[i].pageY - originY) / scale; + + data.setInt32(byteOffset, touches[i].identifier, true); + byteOffset += 4; + data.setUint8(byteOffset, phrases[i]); + byteOffset += 1; + data.setInt16(byteOffset, x, true); + byteOffset += 2; + data.setInt16(byteOffset, y, true); + byteOffset += 2; + data.setFloat32(byteOffset, touches[i].force, true); + byteOffset += 4; + } + _videoPlayer && _videoPlayer.sendMsg(data.buffer); + } + + sendTouchMove = (e) => { + sendTouch(e, PointerPhase.Moved); + e.preventDefault(); + }; + + sendTouchStart = (e) => { + sendTouch(e, PointerPhase.Began); + e.preventDefault(); + }; + + sendTouchEnd = (e) => { + sendTouch(e, PointerPhase.Ended); + e.preventDefault(); + }; + + sendTouchCancel = (e) => { + sendTouch(e, PointerPhase.Canceled); + e.preventDefault(); + }; + + sendMouse = (e) => { + const scale = _videoPlayer.videoScale; + const originX = _videoPlayer.videoOriginX; + const originY = _videoPlayer.videoOriginY; + + const x = (e.clientX - originX) / scale; + // According to Unity Coordinate system + // const y = (e.clientY - originY) / scale; + const y = _videoPlayer.videoHeight - (e.clientY - originY) / scale; + + Logger.log("x: " + x + ", y: " + y + ", scale: " + scale + ", originX: " + originX + ", originY: " + originY + " mouse button:" + e.buttons); + let data = new DataView(new ArrayBuffer(6)); + data.setUint8(0, InputEvent.Mouse); + data.setInt16(1, x, true); + data.setInt16(3, y, true); + data.setUint8(5, e.buttons); + _videoPlayer && _videoPlayer.sendMsg(data.buffer); + }; + + function sendMouseWheel(e) { + Logger.log("mouse wheel with delta " + e.wheelDelta); + let data = new DataView(new ArrayBuffer(9)); + data.setUint8(0, InputEvent.MouseWheel); + data.setFloat32(1, e.deltaX, true); + data.setFloat32(5, e.deltaY, true); + _videoPlayer && _videoPlayer.sendMsg(data.buffer); + } + + // Listen to mouse events + playerElement.addEventListener('click', sendMouse, false); + playerElement.addEventListener('mousedown', sendMouse, false); + playerElement.addEventListener('mouseup', sendMouse, false); + playerElement.addEventListener('mousemove', sendMouse, false); + playerElement.addEventListener('wheel', sendMouseWheel, false); + + // Listen to touch events based on "Touch Events Level1" TR. + // + // Touch event Level1 https://www.w3.org/TR/touch-events/ + // Touch event Level2 https://w3c.github.io/touch-events/ + // + playerElement.addEventListener('touchend', sendTouchEnd, false); + playerElement.addEventListener('touchstart', sendTouchStart, false); + playerElement.addEventListener('touchcancel', sendTouchCancel, false); + playerElement.addEventListener('touchmove', sendTouchMove, false); +} + + +export function unregisterMouseEvents(playerElement) { + + // Stop listening to mouse events + playerElement.removeEventListener('click', sendMouse, false); + playerElement.removeEventListener('mousedown', sendMouse, false); + playerElement.removeEventListener('mouseup', sendMouse, false); + playerElement.removeEventListener('mousemove', sendMouse, false); + playerElement.removeEventListener('wheel', sendMouseWheel, false); + + // Stop listening to touch events based on "Touch Events Level1" TR. + playerElement.removeEventListener('touchend', sendTouchEnd, false); + playerElement.removeEventListener('touchstart', sendTouchStart, false); + playerElement.removeEventListener('touchcancel', sendTouchCancel, false); + playerElement.removeEventListener('touchmove', sendTouchMove, false); + +} + + +export function sendClickEvent(videoPlayer, elementId) { + let data = new DataView(new ArrayBuffer(3)); + data.setUint8(0, InputEvent.ButtonClick); + data.setInt16(1, elementId, true); + videoPlayer && videoPlayer.sendMsg(data.buffer); +} diff --git a/client/public/videoplayer/js/video-player.js b/client/public/videoplayer/js/video-player.js new file mode 100644 index 0000000..19bfaca --- /dev/null +++ b/client/public/videoplayer/js/video-player.js @@ -0,0 +1,246 @@ +import { Signaling, WebSocketSignaling } from "../../module/signaling.js"; +import Peer from "../../module/peer.js"; +import * as Logger from "../../module/logger.js"; + + +// enum type of event sending from Unity +var UnityEventType = { + SWITCH_VIDEO: 0 +}; + +function uuid4() { + var temp_url = URL.createObjectURL(new Blob()); + var uuid = temp_url.toString(); + URL.revokeObjectURL(temp_url); + return uuid.split(/[:/]/g).pop().toLowerCase(); // remove prefixes +} + +export class VideoPlayer { + constructor(elements) { + const _this = this; + this.pc = null; + this.channel = null; + this.connectionId = null; + + // main video + this.localStream = new MediaStream(); + this.video = elements[0]; + this.video.playsInline = true; + this.video.addEventListener('loadedmetadata', function () { + _this.video.play(); + _this.resizeVideo(); + }, true); + + // secondly video + this.localStream2 = new MediaStream(); + this.videoThumb = elements[1]; + this.videoThumb.playsInline = true; + this.videoThumb.addEventListener('loadedmetadata', function () { + _this.videoThumb.play(); + }, true); + + this.videoTrackList = []; + this.videoTrackIndex = 0; + this.maxVideoTrackLength = 2; + + this.ondisconnect = function () { }; + } + + async setupConnection(useWebSocket) { + const _this = this; + // close current RTCPeerConnection + if (this.pc) { + Logger.log('Close current PeerConnection'); + this.pc.close(); + this.pc = null; + } + + if (useWebSocket) { + this.signaling = new WebSocketSignaling(); + } else { + this.signaling = new Signaling(); + } + + this.connectionId = uuid4(); + + // Create peerConnection with proxy server and set up handlers + this.pc = new Peer(this.connectionId, true); + this.pc.addEventListener('disconnect', () => { + _this.ondisconnect(); + }); + this.pc.addEventListener('trackevent', (e) => { + const data = e.detail; + if (data.track.kind == 'video') { + _this.videoTrackList.push(data.track); + } + if (data.track.kind == 'audio') { + _this.localStream.addTrack(data.track); + } + if (_this.videoTrackList.length == _this.maxVideoTrackLength) { + _this.switchVideo(_this.videoTrackIndex); + } + }); + this.pc.addEventListener('sendoffer', (e) => { + const offer = e.detail; + _this.signaling.sendOffer(offer.connectionId, offer.sdp); + }); + this.pc.addEventListener('sendanswer', (e) => { + const answer = e.detail; + _this.signaling.sendAnswer(answer.connectionId, answer.sdp); + }); + this.pc.addEventListener('sendcandidate', (e) => { + const candidate = e.detail; + _this.signaling.sendCandidate(candidate.connectionId, candidate.candidate, candidate.sdpMid, candidate.sdpMLineIndex); + }); + + this.signaling.addEventListener('disconnect', async (e) => { + const data = e.detail; + if (_this.pc != null && _this.pc.connectionId == data.connectionId) { + _this.ondisconnect(); + } + }); + this.signaling.addEventListener('offer', async (e) => { + const offer = e.detail; + const desc = new RTCSessionDescription({ sdp: offer.sdp, type: "offer" }); + if (_this.pc != null) { + await _this.pc.onGotDescription(offer.connectionId, desc); + } + }); + this.signaling.addEventListener('answer', async (e) => { + const answer = e.detail; + const desc = new RTCSessionDescription({ sdp: answer.sdp, type: "answer" }); + if (_this.pc != null) { + await _this.pc.onGotDescription(answer.connectionId, desc); + } + }); + this.signaling.addEventListener('candidate', async (e) => { + const candidate = e.detail; + const iceCandidate = new RTCIceCandidate({ candidate: candidate.candidate, sdpMid: candidate.sdpMid, sdpMLineIndex: candidate.sdpMLineIndex }); + if (_this.pc != null) { + await _this.pc.onGotCandidate(candidate.connectionId, iceCandidate); + } + }); + + // setup signaling + await this.signaling.start(); + + // Create data channel with proxy server and set up handlers + this.channel = this.pc.createDataChannel(this.connectionId, 'data'); + this.channel.onopen = function () { + Logger.log('Datachannel connected.'); + }; + this.channel.onerror = function (e) { + Logger.log("The error " + e.error.message + " occurred\n while handling data with proxy server."); + }; + this.channel.onclose = function () { + Logger.log('Datachannel disconnected.'); + }; + this.channel.onmessage = async (msg) => { + // receive message from unity and operate message + let data; + // receive message data type is blob only on Firefox + if (navigator.userAgent.indexOf('Firefox') != -1) { + data = await msg.data.arrayBuffer(); + } else { + data = msg.data; + } + const bytes = new Uint8Array(data); + _this.videoTrackIndex = bytes[1]; + switch (bytes[0]) { + case UnityEventType.SWITCH_VIDEO: + _this.switchVideo(_this.videoTrackIndex); + break; + } + }; + } + + resizeVideo() { + const clientRect = this.video.getBoundingClientRect(); + const videoRatio = this.videoWidth / this.videoHeight; + const clientRatio = clientRect.width / clientRect.height; + + this._videoScale = videoRatio > clientRatio ? clientRect.width / this.videoWidth : clientRect.height / this.videoHeight; + const videoOffsetX = videoRatio > clientRatio ? 0 : (clientRect.width - this.videoWidth * this._videoScale) * 0.5; + const videoOffsetY = videoRatio > clientRatio ? (clientRect.height - this.videoHeight * this._videoScale) * 0.5 : 0; + this._videoOriginX = clientRect.left + videoOffsetX; + this._videoOriginY = clientRect.top + videoOffsetY; + } + + // switch streaming destination main video and secondly video + switchVideo(indexVideoTrack) { + this.video.srcObject = this.localStream; + this.videoThumb.srcObject = this.localStream2; + + if (indexVideoTrack == 0) { + this.replaceTrack(this.localStream, this.videoTrackList[0]); + this.replaceTrack(this.localStream2, this.videoTrackList[1]); + } + else { + this.replaceTrack(this.localStream, this.videoTrackList[1]); + this.replaceTrack(this.localStream2, this.videoTrackList[0]); + } + } + + // replace video track related the MediaStream + replaceTrack(stream, newTrack) { + const tracks = stream.getVideoTracks(); + for (const track of tracks) { + if (track.kind == 'video') { + stream.removeTrack(track); + } + } + stream.addTrack(newTrack); + } + + get videoWidth() { + return this.video.videoWidth; + } + + get videoHeight() { + return this.video.videoHeight; + } + + get videoOriginX() { + return this._videoOriginX; + } + + get videoOriginY() { + return this._videoOriginY; + } + + get videoScale() { + return this._videoScale; + } + + sendMsg(msg) { + if (this.channel == null) { + return; + } + switch (this.channel.readyState) { + case 'connecting': + Logger.log('Connection not ready'); + break; + case 'open': + this.channel.send(msg); + break; + case 'closing': + Logger.log('Attempt to sendMsg message while closing'); + break; + case 'closed': + Logger.log('Attempt to sendMsg message while connection closed.'); + break; + } + } + + async stop() { + if (this.signaling) { + await this.signaling.stop(); + this.signaling = null; + } + + if (this.pc) { + this.pc.close(); + this.pc = null; + } + } +} diff --git a/client/src/charnumber.js b/client/src/charnumber.js new file mode 100644 index 0000000..2c79237 --- /dev/null +++ b/client/src/charnumber.js @@ -0,0 +1,109 @@ +// KeyboardEvent.charcode is already deprecated. +// +export const CharNumber = { + "Backspace": 8, + "Tab": 9, + "Enter": 13, + "Shift": 16, + "Control": 17, + "Alt": 18, + "Pause": 19, + "CapsLock": 20, + "Escape": 27, + " ": 32, + "!": 33, + "\"": 34, + "#": 35, + "$": 36, + "%": 37, + "&": 38, + "'": 39, + "(": 40, + ")": 41, + "*": 42, + "+": 43, + ",": 44, + "-": 45, + ".": 46, + "/": 47, + "0": 48, + "1": 49, + "2": 50, + "3": 51, + "4": 52, + "5": 53, + "6": 54, + "7": 55, + "8": 56, + "9": 57, + ":": 58, + ";": 59, + "<": 60, + "=": 61, + ">": 62, + "?": 63, + "@": 64, + "A": 65, + "B": 66, + "C": 67, + "D": 68, + "E": 69, + "F": 70, + "G": 71, + "H": 72, + "I": 73, + "J": 74, + "K": 75, + "L": 76, + "M": 77, + "N": 78, + "O": 79, + "P": 80, + "Q": 81, + "R": 82, + "S": 83, + "T": 84, + "U": 85, + "V": 86, + "W": 87, + "X": 88, + "Y": 89, + "Z": 90, + "[": 91, + "\\": 92, + "]": 93, + "^": 94, + "_": 95, + "`": 96, + "a": 97, + "b": 98, + "c": 99, + "d": 100, + "e": 101, + "f": 102, + "g": 103, + "h": 104, + "i": 105, + "j": 106, + "k": 107, + "l": 108, + "m": 109, + "n": 110, + "o": 111, + "p": 112, + "q": 113, + "r": 114, + "s": 115, + "t": 116, + "u": 117, + "v": 118, + "w": 119, + "x": 120, + "y": 121, + "z": 122, + "{": 123, + "|": 124, + "}": 125, + "~": 126, + "Delete": 127 + }; diff --git a/client/src/gamepadbutton.js b/client/src/gamepadbutton.js new file mode 100644 index 0000000..047d248 --- /dev/null +++ b/client/src/gamepadbutton.js @@ -0,0 +1,26 @@ +export const GamepadButton = { + DpadUp: 0, + DpadDown: 1, + DpadLeft: 2, + DpadRight: 3, + North: 4, + East: 5, + South: 6, + West: 7, + LeftStick: 8, + RightStick: 9, + LeftShoulder: 10, + RightShoulder: 11, + Start: 12, + Select: 13, + LeftTrigger: 32, + RightTrigger: 33, + X: 7, // West + Y: 4, // North + A: 6, // South, + B: 5, // East, + Cross: 6, // South, + Square: 7, // West, + Triangle: 4, //North, + Circle: 5 // East, +}; \ No newline at end of file diff --git a/client/src/gamepadhandler.js b/client/src/gamepadhandler.js new file mode 100644 index 0000000..d74dd0b --- /dev/null +++ b/client/src/gamepadhandler.js @@ -0,0 +1,44 @@ +export class GamepadHandler extends EventTarget { + constructor() { + super(); + this._controllers = {}; + window.requestAnimationFrame(this._updateStatus.bind(this)); + } + + /** + * @param {Gamepad} gamepad + */ + addGamepad(gamepad) { + this._controllers[gamepad.index] = gamepad; + } + + /** + * @param {Gamepad} gamepad + */ + removeGamepad(gamepad) { + delete this._controllers[gamepad.index]; + } + + _updateStatus() { + this._scanGamepad(); + for(let i in this._controllers) { + const controller = this._controllers[i]; + + // gamepadupdated event type is own definition + this.dispatchEvent(new GamepadEvent('gamepadupdated', { + gamepad: controller + })); + } + window.requestAnimationFrame(this._updateStatus.bind(this)); + } + + _scanGamepad() { + let gamepads = navigator.getGamepads(); + for (let i = 0; i < gamepads.length; i++) { + if (gamepads[i] && (gamepads[i].index in this._controllers)) { + this._controllers[gamepads[i].index] = gamepads[i]; + } + } + } +} + diff --git a/client/src/inputdevice.js b/client/src/inputdevice.js new file mode 100644 index 0000000..50090f4 --- /dev/null +++ b/client/src/inputdevice.js @@ -0,0 +1,718 @@ +import { + MemoryHelper, +} from "./memoryhelper.js"; + +import { CharNumber } from "./charnumber.js"; +import { Keymap } from "./keymap.js"; +import { MouseButton } from "./mousebutton.js"; +import { GamepadButton } from "./gamepadbutton.js"; +import { TouchPhase } from "./touchphase.js"; +import { TouchFlags } from "./touchflags.js"; + +export class FourCC { + /** + * {Number} _code; + */ + + /** + * + * @param {String} a + * @param {String} b + * @param {String} c + * @param {String} d + */ + constructor(a, b, c, d) { + this._code = (a.charCodeAt() << 24) + | (b.charCodeAt() << 16) + | (c.charCodeAt() << 8) + | d.charCodeAt(); + } + + /** + * @returns {Number} + */ + toInt32() { + return this._code; + } +} + + +export class InputDevice { + + /** + * + * name; + * layout; + * deviceId; + * usages; + * description; + * + * _inputState; + */ + + /** + * + * @param {Number} name + * @param {String} layout + * @param {Number} deviceId + * @param {String[]} usages + * @param {Object} description + */ + constructor(name, layout, deviceId, usages, description) { + this.name = name; + this.layout = layout; + this.deviceId = deviceId; + this.usages = usages; + this.description = description; + + this._inputState = null; + } + + /** + * + * @param {IInputState} state + */ + updateState(state) { + this._inputState = state; + } + + queueEvent(event) { + throw new Error(`Please implement this method. event:${event}`); + } + + /** + * @returns {IInputState} + */ + get currentState() { + return this._inputState; + } +} + +export class Mouse extends InputDevice { + /** + * @param {(MouseEvent|WheelEvent)} event + */ + queueEvent(event) { + this.updateState(new MouseState(event)); + } +} + +export class Keyboard extends InputDevice { + static get keycount() { return 110; } + /** + * + * @param {KeyboardEvent} event + */ + queueEvent(event) { + this.updateState(new KeyboardState(event, this.currentState)); + } +} + +export class Touchscreen extends InputDevice { + /** + * @param {TouchScreenEvent} event + */ + queueEvent(event, time) { + this.updateState(new TouchscreenState(event, this.currentState, time)); + } +} + +export class Gamepad extends InputDevice { + /** + * @param {GamepadButtonEvent | GamepadAxisEvent} event + */ + queueEvent(event) { + this.updateState(new GamepadState(event)); + } +} + +export class InputEvent { + static get invalidEventId() { return 0; } + static get size() { return 20; } + + /** + * field offset 0 + * @member {Number} type; + * + * field offset 4 + * @member {Number} sizeInBytes; + * + * field offset 6 + * @member {Number} deviceId; + * + * field offset 8 + * @member {Number} time; + * + * field offset 16 + * @member {Number} eventId; + */ + + /** + * + * @param {Number} type + * @param {Number} sizeInBytes + * @param {Number} deviceId + * @param {Number} time + */ + constructor(type, sizeInBytes, deviceId, time) { + this.type = type; + this.sizeInBytes = sizeInBytes; + this.deviceId = deviceId; + this.time = time; + this.eventId = InputEvent.invalidEventId; + } + + /** + * @returns {ArrayBuffer} + */ + get buffer() { + let _buffer = new ArrayBuffer(InputEvent.size); + let view = new DataView(_buffer); + view.setInt32(0, this.type, true); + view.setInt16(4, this.sizeInBytes, true); + view.setInt16(6, this.deviceId, true); + view.setFloat64(8, this.time, true); + view.setInt16(16, this.sizeInBytes, true); + return _buffer; + } +} + +export class IInputState { + /** + * @returns {ArrayBuffer} + */ + get buffer() { + throw new Error('Please implement this field'); + } + /** + * @returns {Number} + */ + get format() { + throw new Error('Please implement this field'); + } +} + +export class MouseState extends IInputState { + static get size() { return 30; } + static get format() { return new FourCC('M', 'O', 'U', 'S').toInt32(); } + + /** + * field offset 0 + * @member {Array} position; + * + * field offset 8 + * @member {Array} delta; + * + * field offset 16 + * @member {Array} scroll; + * + * field offset 24 + * @member {ArrayBuffer} buttons; + * + * field offset 26 + * @member {Array} displayIndex; + * + * field offset 28 + * @member {Array} clickCount; + */ + + /** + * @param {MouseEvent | WheelEvent} event + */ + constructor(event) { + super(); + + this.position = [event.clientX, event.clientY]; + this.delta = [event.movementX, -event.movementY]; + this.scroll = [0, 0]; + if(event.type === 'wheel') { + this.scroll = [event.deltaX, event.deltaY]; + } + this.buttons = new ArrayBuffer(2); + + const left = event.buttons & 1 << 0; + const right = event.buttons & 1 << 1; + const middle = event.buttons & 1 << 2; + const back = event.buttons & 1 << 3; + const forward = event.buttons & 1 << 4; + + MemoryHelper.writeSingleBit(this.buttons, MouseButton.Left, left); + MemoryHelper.writeSingleBit(this.buttons, MouseButton.Right, right); + MemoryHelper.writeSingleBit(this.buttons, MouseButton.Middle, middle); + MemoryHelper.writeSingleBit(this.buttons, MouseButton.Forward, forward); + MemoryHelper.writeSingleBit(this.buttons, MouseButton.Back, back); + } + + /** + * @returns {ArrayBuffer} + */ + get buffer() { + const size = MouseState.size; + const buttons = new Uint16Array(this.buttons)[0]; + let _buffer = new ArrayBuffer(size); + let view = new DataView(_buffer); + view.setFloat32(0, this.position[0], true); + view.setFloat32(4, this.position[1], true); + view.setFloat32(8, this.delta[0], true); + view.setFloat32(12, this.delta[1], true); + view.setFloat32(16, this.scroll[0], true); + view.setFloat32(20, this.scroll[1], true); + view.setUint16(24, buttons, true); + view.setUint16(26, this.displayIndex, true); + view.setUint16(28, this.clickCount, true); + return _buffer; + } + + /** + * @returns {Number} + */ + get format() { + return MouseState.format; + } +} + +export class KeyboardState extends IInputState { + static get sizeInBits() { return Keyboard.keycount; } + static get sizeInBytes() { return (KeyboardState.sizeInBits + 7) >> 3; } + static get format() { return new FourCC('K', 'E', 'Y', 'S').toInt32(); } + + /** + * field offset 0 + * @number {ArrayBuffer} keys; + */ + + /** + * @param {KeyboardEvent} event + */ + constructor(event, state) { + super(); + if (state == null || state.keys == null) { + this.keys = new ArrayBuffer(KeyboardState.sizeInBytes); + } else { + this.keys = state.keys; + } + let value = false; + switch(event.type) { + case 'keydown': + value = true; + break; + case 'keyup': + value = false; + break; + default: + throw new Error(`unknown event type ${event.type})`); + } + const key = Keymap[event.code]; + MemoryHelper.writeSingleBit(this.keys, key, value); + } + + /** + * @returns {ArrayBuffer} + */ + get buffer() { + return this.keys; + } + + /** + * @returns {Number} + */ + get format() { + return KeyboardState.format; + } +} + +export class TouchState { + static get format() { return new FourCC('T', 'O', 'U', 'C').toInt32(); } + static get size() { return 56; } + static incrementTouchId() { + if(TouchState._currentTouchId === undefined) { + TouchState._currentTouchId = 0; + } + return ++TouchState._currentTouchId; + } + static prevTouches() { + if(TouchState._prevTouches === undefined) { + // max touch count is 10 + TouchState._prevTouches = new Array(10); + } + return TouchState._prevTouches; + } + + /** + * field offset 0 + * @number {Number} touchId; + * field offset 4 + * @number {Number[]} position; + * field offset 12 + * @number {Number[]} delta; + * field offset 20 + * @number {Number} pressure; + * field offset 24 + * @number {Number[]} radius; + * field offset 32 + * @number {Number} phase; + * field offset 33 + * @number {Number} tapCount; + * field offset 34 + * @number {Number} displayIndex; + * field offset 35 + * @number {Number} flag; + * field offset 36 + * @number {Number} padding; + * field offset 40 + * @number {Number} startTime; + * field offset 48 + * @number {Number[]} startPosition; + */ + + + /** + * @param {Touch} touchId + * @param {TouchState} prevState + * @param {Number[]} position + * @param {Number} pressure + * @param {Number[]} radius + * @param {TouchPhase} phaseId + * @param {Number} time + */ + constructor(touchId, prevState, position, pressure, radius, phaseId, time) { + this.touchId = touchId; + this.position = position != null ? position.slice() : null; + if(phaseId == TouchPhase.Moved) { + this.delta = [this.position[0] - prevState.position[0], this.position[1] - prevState.position[1]]; + } else { + this.delta = [0, 0]; + } + this.pressure = pressure; + this.radius = radius != null ? radius.slice(): null; + this.phaseId = phaseId; + this.tapCount = 0; + this.displayIndex = 0; + this.flags = 0; + this.padding = 0; + if(phaseId == TouchPhase.Began) { + this.startTime = time; + this.startPosition = this.position.slice(); + } else { + this.startTime = prevState != null ? prevState.startTime : null; + this.startPosition = prevState != null ? prevState.startPosition.slice() : null; + } + } + + + copy() { + let state = new TouchState(); + state.touchId = this.touchId; + state.position = this.position.slice(); + state.delta = this.delta.slice(); + state.pressure = this.pressure; + state.radius = this.radius.slice(); + state.phaseId = this.phaseId; + state.tapCount = this.tapCount; + state.displayIndex = this.displayIndex; + state.flags = this.flags; + state.padding = this.padding; + state.startTime = this.startTime; + state.startPosition = this.startPosition.slice(); + return state; + } + + /** + * @returns {ArrayBuffer} + */ + get buffer() { + const size = TouchState.size; // todo + let _buffer = new ArrayBuffer(size); + let view = new DataView(_buffer); + + view.setInt32(0, this.touchId, true); + view.setFloat32(4, this.position[0], true); + view.setFloat32(8, this.position[1], true); + view.setFloat32(12, this.delta[0], true); + view.setFloat32(16, this.delta[1], true); + view.setFloat32(20, this.pressure, true); + view.setFloat32(24, this.radius[0], true); + view.setFloat32(28, this.radius[1], true); + view.setInt8(32, this.phaseId, true); + view.setInt8(33, this.tapCount, true); + view.setInt8(34, this.displayIndex, true); + view.setInt8(35, this.flags, true); + view.setInt32(36, this.padding, true); + view.setFloat64(40, this.startTime, true); + view.setFloat32(48, this.startPosition[0], true); + view.setFloat32(52, this.startPosition[1], true); + return _buffer; + } + + /** + * @returns {Number} + */ + get format() { + return TouchState.format; + } +} + +export class TouchscreenState extends IInputState { + static get maxTouches() { return 10; } + static get format() { return new FourCC('T', 'S', 'C', 'R').toInt32(); } + static convertPhaseId(type) { + let phaseId = TouchPhase.Stationary; + switch(type) { + case 'touchstart': + phaseId = TouchPhase.Began; break; + case 'touchend': + phaseId = TouchPhase.Ended; break; + case 'touchmove': + phaseId = TouchPhase.Moved; break; + case 'touchcancel': + phaseId = TouchPhase.Canceled; break; + } + return phaseId; + } + + /** + * @param {TouchEvent} event + * @param {TouchScreenState} state + * @param {Date} time + */ + constructor(event, state, time) { + super(); + + switch(event.type) { + // `click` event is called when releasing mouse button or finger on screen. + case 'click' : { + this.touchData = new Array(state.touchData.length); + for(let i = 0; i < state.touchData.length; i++) { + this.touchData[i] = state.touchData[i]; + if(this.touchData[i].phaseId == TouchPhase.Ended) { + this.touchData[i].tapCount = 1; + this.touchData[i].flags |= TouchFlags.Tap; + } + } + break; + } + default: { + let touches = event.changedTouches; + this.touchData = new Array(touches.length); + for(let i = 0; i < touches.length; i++) { + const touch = touches[i]; + const position = [touch.clientX, touch.clientY]; + const phaseId = TouchscreenState.convertPhaseId(event.type); + const pressure = touch.force; + const radius = [touch.radiusX, touch.radiusY]; + + // `touchId` in InputSystem must be set uniquely. + // The numbers of `touch.identifier` in Web API are reused, so these are not unique. + const touchId = phaseId == TouchPhase.Began ? TouchState.incrementTouchId() : TouchState.prevTouches()[touch.identifier].touchId; + const prevState = phaseId != TouchPhase.Began ? TouchState.prevTouches()[touch.identifier] : null; + const touchData = new TouchState(touchId, prevState, position, pressure, radius, phaseId, time); + + // cache state + TouchState.prevTouches()[touch.identifier] = touchData; + this.touchData[i] = touchData; + } + break; + } + } + } + + /** + * @returns {ArrayBuffer} + */ + get buffer() { + const size = TouchState.size * this.touchData.length; + let _buffer = new ArrayBuffer(size); + let view = new Uint8Array(_buffer); + for(let i = 0; i < this.touchData.length; i++) { + view.set(new Uint8Array(this.touchData[i].buffer), TouchState.size * i); + } + return _buffer; + } + + /** + * @returns {Number} + */ + get format() { + return TouchscreenState.format; + } +} + +export class GamepadState extends IInputState { + static get size() { return 28; } + static get format() { return new FourCC('G', 'P', 'A', 'D').toInt32(); } + + /** + * field offset 0 + * @member buttons; + * + * field offset 4 + * @member leftStick; + * + * field offset 12 + * @member rightStick; + * + * field offset 20 + * @member leftTrigger; + * + * field offset 24 + * @member rightTrigger; + */ + + /** + * + * @param {GamepadButtonEvent | GamepadAxisEvent} event + */ + constructor(event) { + super(); + const gamepad = event.gamepad; + const buttons = event.gamepad.buttons; + + this.buttons = new ArrayBuffer(4); + this.leftStick = [ gamepad.axes[0], -gamepad.axes[1] ]; + this.rightStick = [ gamepad.axes[2], -gamepad.axes[3] ]; + this.leftTrigger = buttons[6].value; + this.rightTrigger = buttons[7].value; + + // see https://w3c.github.io/gamepad/#remapping + MemoryHelper.writeSingleBit(this.buttons, GamepadButton.A, buttons[0].pressed); + MemoryHelper.writeSingleBit(this.buttons, GamepadButton.B, buttons[1].pressed); + MemoryHelper.writeSingleBit(this.buttons, GamepadButton.X, buttons[2].pressed); + MemoryHelper.writeSingleBit(this.buttons, GamepadButton.Y, buttons[3].pressed); + MemoryHelper.writeSingleBit(this.buttons, GamepadButton.LeftShoulder, buttons[4].pressed); + MemoryHelper.writeSingleBit(this.buttons, GamepadButton.RightShoulder, buttons[5].pressed); + MemoryHelper.writeSingleBit(this.buttons, GamepadButton.LeftTrigger, buttons[6].pressed); + MemoryHelper.writeSingleBit(this.buttons, GamepadButton.RightTrigger, buttons[7].pressed); + MemoryHelper.writeSingleBit(this.buttons, GamepadButton.Select, buttons[8].pressed); + MemoryHelper.writeSingleBit(this.buttons, GamepadButton.Start, buttons[9].pressed); + MemoryHelper.writeSingleBit(this.buttons, GamepadButton.LeftStick, buttons[10].pressed); + MemoryHelper.writeSingleBit(this.buttons, GamepadButton.RightStick, buttons[11].pressed); + MemoryHelper.writeSingleBit(this.buttons, GamepadButton.DpadUp, buttons[12].pressed); + MemoryHelper.writeSingleBit(this.buttons, GamepadButton.DpadDown, buttons[13].pressed); + MemoryHelper.writeSingleBit(this.buttons, GamepadButton.DpadLeft, buttons[14].pressed); + MemoryHelper.writeSingleBit(this.buttons, GamepadButton.DpadRight, buttons[15].pressed); + } + + /** + * @returns {ArrayBuffer} + */ + get buffer() { + const size = GamepadState.size; + let _buffer = new ArrayBuffer(size); + let view = new DataView(_buffer); + view.setUint32(0, new Uint32Array(this.buttons)[0], true); + view.setFloat32(4, this.leftStick[0], true); + view.setFloat32(8, this.leftStick[1], true); + view.setFloat32(12, this.rightStick[0], true); + view.setFloat32(16, this.rightStick[1], true); + view.setFloat32(20, this.leftTrigger, true); + view.setFloat32(24, this.rightTrigger, true); + return _buffer; + } + + /** + * @returns {Number} + */ + get format() { + return GamepadState.format; + } +} + +export class TextEvent { + static get format() { return new FourCC('T', 'E', 'X', 'T').toInt32(); } + + /** + * field offset 0 + * @member {InputEvent} baseEvent; + * + * field offset 20 + * @member {Number} character; + */ + + /** + * + * @param {Number} deviceId + * @param {KeyboardEvent} event + * @param {Number} time + * @returns {TextEvent} + + */ + static create(deviceId, event, time) { + const eventSize = InputEvent.size + MemoryHelper.sizeOfInt; + + let textEvent = new TextEvent(); + textEvent.baseEvent = new InputEvent(TextEvent.format, eventSize, deviceId, time); + textEvent.character = CharNumber[event.key]; + return textEvent; + } + + /** + * @returns {ArrayBuffer} + */ + get buffer() { + const size = InputEvent.size + MemoryHelper.sizeOfInt; + let _buffer = new ArrayBuffer(size); + let arrayView = new Uint8Array(_buffer); + let dataView = new DataView(_buffer); + arrayView.set(new Uint8Array(this.baseEvent.buffer), 0); + dataView.setInt32(InputEvent.size, this.character, true); + return _buffer; + } +} + +export class StateEvent { + static get format() { return new FourCC('S', 'T', 'A', 'T').toInt32(); } + + /** + * field offset 0 + * @member {InputEvent} baseEvent; + * + * field offset 20 + * @member {Number} stateFormat; + * + * field offset 24 + * @member {ArrayBuffer} stateData; + */ + + /** + * + * @param {InputDevice} device + * @param {Number} time + * @returns {StateEvent} + */ + static from(device, time) { + return StateEvent.fromState(device.currentState, device.deviceId, time); + } + + /** + * + * @param {IInputState} state + * @param {Number} deviceId + * @param {Number} time + */ + static fromState(state, deviceId, time) { + const stateData = state.buffer; + const stateSize = stateData.byteLength; + const eventSize = InputEvent.size + MemoryHelper.sizeOfInt + stateSize; + + let stateEvent = new StateEvent(); + stateEvent.baseEvent = new InputEvent(StateEvent.format, eventSize, deviceId, time); + stateEvent.stateFormat = state.format; + stateEvent.stateData = stateData; + return stateEvent; + } + + /** + * @returns {ArrayBuffer} + */ + get buffer() { + const stateSize = this.stateData.byteLength; + const size = InputEvent.size + MemoryHelper.sizeOfInt + stateSize; + let _buffer = new ArrayBuffer(size); + let uint8View = new Uint8Array(_buffer); + let dataView = new DataView(_buffer); + uint8View.set(new Uint8Array(this.baseEvent.buffer), 0); + dataView.setInt32(InputEvent.size, this.stateFormat, true); + uint8View.set(new Uint8Array(this.stateData), InputEvent.size+MemoryHelper.sizeOfInt); + return _buffer; + } +} diff --git a/client/src/inputremoting.js b/client/src/inputremoting.js new file mode 100644 index 0000000..f1f82e6 --- /dev/null +++ b/client/src/inputremoting.js @@ -0,0 +1,299 @@ +import { + StateEvent, +} from "./inputdevice.js"; + +import { + MemoryHelper +} from "./memoryhelper.js"; + +export class LocalInputManager { + constructor() { + this._onevent = new EventTarget(); + } + + /** + * event type 'event', 'changedeviceusage' + * @return {Event} + */ + get onEvent() { + return this._onevent; + } + + /** + * @return {Event} + */ + get devices() { + throw new Error(`Please implement this method.`); + } + + /** + * @return {Number} time (sec) + */ + get startTime() { + return this._startTime; + } + + /** + * @return {Number} time (sec) + */ + get timeSinceStartup() { + return Date.now()/1000 - this.startTime; + } + + /** + * @param {Number} time (sec) + */ + setStartTime(time) { + this._startTime = time; + } +} + +export const InputDeviceChange = { + Added: 0, + Removed: 1, + Disconnected: 2, + Reconnected: 3, + Enabled: 4, + Disabled: 5, + UsageChanged: 6, + ConfigurationChanged: 7, + Destroyed: 8, +}; + +export class InputRemoting { + /** + * @param {LocalInputManager} manager + */ + constructor(manager) { + this._localManager = manager; + this._subscribers = new Array(); + this._sending = false; + } + + startSending() { + if(this._sending) { + return; + } + this._sending = true; + + const onEvent = e => { + this._sendEvent(e.detail.event); + }; + + const onDeviceChange = e => { + this._sendDeviceChange(e.detail.device, e.detail.change); + }; + + this._localManager.setStartTime(Date.now()/1000); + this._localManager.onEvent.addEventListener("event", onEvent); + this._localManager.onEvent.addEventListener("changedeviceusage", onDeviceChange); + this._sendInitialMessages(); + } + + stopSending() { + if (!this._sending) { + return; + } + this._sending = false; + } + + /** + * + * @param {Observer} observer + */ + subscribe(observer) { + this._subscribers.push(observer); + } + + _sendInitialMessages() { + this._sendAllGeneratedLayouts(); + this._sendAllDevices(); + } + + _sendAllGeneratedLayouts() { + // todo: + } + + _sendAllDevices() { + var devices = this._localManager.devices; + if(devices == null) + return; + for (const device of devices) { + this._sendDevice(device); + } + } + + _sendDevice(device) { + const newDeviceMessage = NewDeviceMsg.create(device); + this._send(newDeviceMessage); + + // Send current state. We do this here in this case as the device + // may have been added some time ago and thus have already received events. + + // todo: + // const stateEventMessage = NewEventsMsg.createStateEvent(device); + // this._send(stateEventMessage); + } + + _sendEvent(event) { + const message = NewEventsMsg.create(event); + this._send(message); + } + + _sendDeviceChange(device, change) { + if (this._subscribers == null) + return; + + let msg = null; + switch (change) { + case InputDeviceChange.Added: + msg = NewDeviceMsg.Create(device); + break; + case InputDeviceChange.Removed: + msg = RemoveDeviceMsg.Create(device); + break; + case InputDeviceChange.UsageChanged: + msg = ChangeUsageMsg.Create(device); + break; + default: + return; + } + this._send(msg); + } + + _send(message) { + for(let subscriber of this._subscribers) { + subscriber.onNext(message); + } + } +} + +export const MessageType = { + Connect: 0, + Disconnect: 1, + NewLayout: 2, + NewDevice: 3, + NewEvents: 4, + RemoveDevice: 5, + RemoveLayout: 6, + ChangeUsages: 7, + StartSending: 8, + StopSending: 9, +}; + +export class Message { + /** + * field offset 0 + * {Number} participant_id; + * + * field offset 4 + * {Number} type; + * + * field offset 8 + * {Number} length; + * + * field offset 12 + * {ArrayBuffer} data; + */ + + /** + * + * @param {number} participantId + * @param {MessageType} type + * @param {ArrayBuffer} data + */ + constructor(participantId, type, data) { + this.participant_id = participantId; + this.type = type; + this.length = data.byteLength; + this.data = data; + } + + /** + * + * @returns {ArrayBuffer} + */ + get buffer() { + const totalSize = + MemoryHelper.sizeOfInt + // size of this.participant_id + MemoryHelper.sizeOfInt + // size of this.type + MemoryHelper.sizeOfInt + // size of this.length + this.data.byteLength; // size of this.data + + let buffer = new ArrayBuffer(totalSize); + let dataView = new DataView(buffer); + let uint8view = new Uint8Array(buffer); + dataView.setUint32(0, this.participant_id, true); + dataView.setUint32(4, this.type, true); + dataView.setUint32(8, this.length, true); + uint8view.set(new Uint8Array(this.data), 12); + return buffer; + } +} + +export class NewDeviceMsg { + /** + * @param {InputDevice} device + * @returns {Message} + */ + static create(device) { + const data = { + name: device.name, + layout: device.layout, + deviceId: device.deviceId, + variants: device.variants, + description: device.description + }; + const json = JSON.stringify(data); + let buffer = new ArrayBuffer(json.length*2); // 2 bytes for each char + let view = new Uint8Array(buffer); + const length = json.length; + for (let i = 0; i < length; i++) { + view[i] = json.charCodeAt(i); + } + return new Message(0, MessageType.NewDevice, buffer); + } +} + +export class NewEventsMsg { + /** + * + * @param {InputDevice} device + * @returns {Message} + */ + static createStateEvent(device) { + const events = StateEvent.from(device); + return NewEventsMsg.create(events); + } + + /** + * + * @param {StateEvent} event + * @returns {Message} + */ + static create(event) { + return new Message(0, MessageType.NewEvents, event.buffer); + } +} + +export class RemoveDeviceMsg { + /** + * + * @param {InputDevice} device + * @returns {Message} + */ + static create(device) { + let buffer = new ArrayBuffer(MemoryHelper.sizeOfInt); + let view = new DataView(buffer); + view.setInt32(device.deviceId); + return new Message(0, MessageType.RemoveDevice, buffer); + } +} + +export class ChangeUsageMsg { + + static create(device) { + // todo: + throw new Error(`ChangeUsageMsg class is not implemented. device=${device}`); + } +} diff --git a/client/src/keymap.js b/client/src/keymap.js new file mode 100644 index 0000000..5dbca1e --- /dev/null +++ b/client/src/keymap.js @@ -0,0 +1,120 @@ +export const Keymap = { + "Space": 1, + "Enter": 2, + "Tab": 3, + "Backquote": 4, + "Quote": 5, + "Semicolon": 6, + "Comma": 7, + "Period": 8, + "Slash": 9, + "Backslash": 10, + "BracketLeft": 11, + "BracketRight": 12, + "Minus": 13, + "Equal": 14, + "KeyA": 15, + "KeyB": 16, + "KeyC": 17, + "KeyD": 18, + "KeyE": 19, + "KeyF": 20, + "KeyG": 21, + "KeyH": 22, + "KeyI": 23, + "KeyJ": 24, + "KeyK": 25, + "KeyL": 26, + "KeyM": 27, + "KeyN": 28, + "KeyO": 29, + "KeyP": 30, + "KeyQ": 31, + "KeyR": 32, + "KeyS": 33, + "KeyT": 34, + "KeyU": 35, + "KeyV": 36, + "KeyW": 37, + "KeyX": 38, + "KeyY": 39, + "KeyZ": 40, + "Digit1": 41, + "Digit2": 42, + "Digit3": 43, + "Digit4": 44, + "Digit5": 45, + "Digit6": 46, + "Digit7": 47, + "Digit8": 48, + "Digit9": 49, + "Digit0": 50, + "ShiftLeft": 51, + "ShiftRight": 52, + "AltLeft": 53, + "AltRight": 54, + // "AltGr": 54, + "ControlLeft": 55, + "ControlRight": 56, + "MetaLeft": 57, + "MetaRight": 58, + // "LeftWindows": 57, + // "RightWindows": 58, + // "LeftApple": 57, + // "RightApple": 58, + // "LeftCommand": 57, + // "RightCommand": 58, + "ContextMenu": 59, + "Escape": 60, + "ArrowLeft": 61, + "ArrowRight": 62, + "ArrowUp": 63, + "ArrowDown": 64, + "Backspace": 65, + "PageDown": 66, + "PageUp": 67, + "Home": 68, + "End": 69, + "Insert": 70, + "Delete": 71, + "CapsLock": 72, + "NumLock": 73, + "PrintScreen": 74, + "ScrollLock": 75, + "Pause": 76, + "NumpadEnter": 77, + "NumpadDivide": 78, + "NumpadMultiply": 79, + "NumpadAdd": 80, + "NumpadSubtract": 81, + "NumpadDecimal": 82, + "NumpadEquals": 83, + "Numpad0": 84, + "Numpad1": 85, + "Numpad2": 86, + "Numpad3": 87, + "Numpad4": 88, + "Numpad5": 89, + "Numpad6": 90, + "Numpad7": 91, + "Numpad8": 92, + "Numpad9": 93, + "F1": 94, + "F2": 95, + "F3": 96, + "F4": 97, + "F5": 98, + "F6": 99, + "F7": 100, + "F8": 101, + "F9": 102, + "F10": 103, + "F11": 104, + "F12": 105, + // "OEM1": 106, + // "OEM2": 107, + // "OEM3": 108, + // "OEM4": 109, + // "OEM5": 110, + // "IMESelected": 111, + }; \ No newline at end of file diff --git a/client/src/logger.js b/client/src/logger.js new file mode 100644 index 0000000..359ab39 --- /dev/null +++ b/client/src/logger.js @@ -0,0 +1,29 @@ +let isDebug = false; + +export function enable() { + isDebug = true; +} + +export function disable() { + isDebug = false; +} + +export function debug(msg) { + isDebug && console.debug(msg); +} + +export function info(msg) { + isDebug && console.info(msg); +} + +export function log(msg) { + isDebug && console.log(msg); +} + +export function warn(msg) { + isDebug && console.warn(msg); +} + +export function error(msg) { + isDebug && console.error(msg); +} diff --git a/client/src/memoryhelper.js b/client/src/memoryhelper.js new file mode 100644 index 0000000..3021008 --- /dev/null +++ b/client/src/memoryhelper.js @@ -0,0 +1,28 @@ +export class MemoryHelper { + /** + * @param {ArrayBuffer} buffer + * @param {number} bitOffset + * @param {boolean} value + */ + static writeSingleBit(buffer, bitOffset, value) { + let view = new Uint8Array(buffer); + const index = Math.floor(bitOffset / 8); + bitOffset = bitOffset % 8; + const byte = view[index]; + let newByte = 1 << bitOffset; + if(value) { + newByte = newByte | byte; + } + else { + newByte = ~newByte & byte; + } + view[index] = newByte; + } + + /** + * @return {Number} + */ + static get sizeOfInt() { + return 4; + } +} \ No newline at end of file diff --git a/client/src/mousebutton.js b/client/src/mousebutton.js new file mode 100644 index 0000000..0ed88f7 --- /dev/null +++ b/client/src/mousebutton.js @@ -0,0 +1,7 @@ +export const MouseButton = { + Left: 0, + Right: 1, + Middle: 2, + Foaward: 3, + Back: 4, +}; \ No newline at end of file diff --git a/client/src/peer.js b/client/src/peer.js new file mode 100644 index 0000000..da379a0 --- /dev/null +++ b/client/src/peer.js @@ -0,0 +1,187 @@ +import * as Logger from "./logger.js"; + +export default class Peer extends EventTarget { + constructor(connectionId, polite, config, resendIntervalMsec = 5000) { + super(); + const _this = this; + this.connectionId = connectionId; + this.polite = polite; + this.config = config; + this.pc = new RTCPeerConnection(this.config); + this.makingOffer = false; + this.waitingAnswer = false; + this.ignoreOffer = false; + this.srdAnswerPending = false; + this.log = str => void Logger.log(`[${_this.polite ? 'POLITE' : 'IMPOLITE'}] ${str}`); + this.warn = str => void Logger.warn(`[${_this.polite ? 'POLITE' : 'IMPOLITE'}] ${str}`); + this.assert_equals = window.assert_equals ? window.assert_equals : (a, b, msg) => { if (a === b) { return; } throw new Error(`${msg} expected ${b} but got ${a}`); }; + this.interval = resendIntervalMsec; + this.sleep = msec => new Promise(resolve => setTimeout(resolve, msec)); + + this.pc.ontrack = e => { + _this.log(`ontrack:${e}`); + _this.dispatchEvent(new CustomEvent('trackevent', { detail: e })); + }; + this.pc.ondatachannel = e => { + _this.log(`ondatachannel:${e}`); + _this.dispatchEvent(new CustomEvent('adddatachannel', { detail: e })); + }; + this.pc.onicecandidate = ({ candidate }) => { + _this.log(`send candidate:${candidate}`); + if (candidate == null) { + return; + } + _this.dispatchEvent(new CustomEvent('sendcandidate', { detail: { connectionId: _this.connectionId, candidate: candidate.candidate, sdpMLineIndex: candidate.sdpMLineIndex, sdpMid: candidate.sdpMid } })); + }; + + this.pc.onnegotiationneeded = this._onNegotiation.bind(this); + + this.pc.onsignalingstatechange = () => { + _this.log(`signalingState changed:${_this.pc.signalingState}`); + }; + + this.pc.oniceconnectionstatechange = () => { + _this.log(`iceConnectionState changed:${_this.pc.iceConnectionState}`); + if (_this.pc.iceConnectionState === 'failed') { + this.dispatchEvent(new Event('disconnect')); + } + }; + + this.pc.onicegatheringstatechange = () => { + _this.log(`iceGatheringState changed:${_this.pc.iceGatheringState}'`); + }; + + this.loopResendOffer(); + } + + async _onNegotiation() { + try { + this.log(`SLD due to negotiationneeded`); + this.assert_equals(this.pc.signalingState, 'stable', 'negotiationneeded always fires in stable state'); + this.assert_equals(this.makingOffer, false, 'negotiationneeded not already in progress'); + this.makingOffer = true; + await this.pc.setLocalDescription(); + this.assert_equals(this.pc.signalingState, 'have-local-offer', 'negotiationneeded not racing with onmessage'); + this.assert_equals(this.pc.localDescription.type, 'offer', 'negotiationneeded SLD worked'); + this.waitingAnswer = true; + this.dispatchEvent(new CustomEvent('sendoffer', { detail: { connectionId: this.connectionId, sdp: this.pc.localDescription.sdp } })); + } catch (e) { + this.log(e); + } finally { + this.makingOffer = false; + } + } + + async loopResendOffer() { + while (this.connectionId) { + if (this.pc && this.waitingAnswer) { + this.dispatchEvent(new CustomEvent('sendoffer', { detail: { connectionId: this.connectionId, sdp: this.pc.localDescription.sdp } })); + } + await this.sleep(this.interval); + } + } + + close() { + this.connectionId = null; + if (this.pc) { + this.pc.close(); + this.pc = null; + } + } + + getTransceivers(connectionId) { + if (this.connectionId != connectionId) { + return null; + } + + return this.pc.getTransceivers(); + } + + addTrack(connectionId, track) { + if (this.connectionId != connectionId) { + return null; + } + + return this.pc.addTrack(track); + } + + addTransceiver(connectionId, trackOrKind, init) { + if (this.connectionId != connectionId) { + return null; + } + + return this.pc.addTransceiver(trackOrKind, init); + } + + createDataChannel(connectionId, label) { + if (this.connectionId != connectionId) { + return null; + } + + return this.pc.createDataChannel(label); + } + + async getStats(connectionId) { + if (this.connectionId != connectionId) { + return null; + } + + return await this.pc.getStats(); + } + + async onGotDescription(connectionId, description) { + if (this.connectionId != connectionId) { + return; + } + + const _this = this; + const isStable = + this.pc.signalingState == 'stable' || + (this.pc.signalingState == 'have-local-offer' && this.srdAnswerPending); + this.ignoreOffer = + description.type == 'offer' && !this.polite && (this.makingOffer || !isStable); + + if (this.ignoreOffer) { + _this.log(`glare - ignoring offer`); + return; + } + + this.waitingAnswer = false; + this.srdAnswerPending = description.type == 'answer'; + _this.log(`SRD(${description.type})`); + await this.pc.setRemoteDescription(description); + this.srdAnswerPending = false; + + if (description.type == 'offer') { + _this.dispatchEvent(new CustomEvent('ongotoffer', { detail: { connectionId: _this.connectionId } })); + + _this.assert_equals(this.pc.signalingState, 'have-remote-offer', 'Remote offer'); + _this.assert_equals(this.pc.remoteDescription.type, 'offer', 'SRD worked'); + _this.log('SLD to get back to stable'); + await this.pc.setLocalDescription(); + _this.assert_equals(this.pc.signalingState, 'stable', 'onmessage not racing with negotiationneeded'); + _this.assert_equals(this.pc.localDescription.type, 'answer', 'onmessage SLD worked'); + _this.dispatchEvent(new CustomEvent('sendanswer', { detail: { connectionId: _this.connectionId, sdp: _this.pc.localDescription.sdp } })); + + } else { + _this.dispatchEvent(new CustomEvent('ongotanswer', { detail: { connectionId: _this.connectionId } })); + + _this.assert_equals(this.pc.remoteDescription.type, 'answer', 'Answer was set'); + _this.assert_equals(this.pc.signalingState, 'stable', 'answered'); + this.pc.dispatchEvent(new Event('negotiated')); + } + } + + async onGotCandidate(connectionId, candidate) { + if (this.connectionId != connectionId) { + return; + } + + try { + await this.pc.addIceCandidate(candidate); + } catch (e) { + if (this.pc && !this.ignoreOffer) + this.warn(`${this.pc} this candidate can't accept current signaling state ${this.pc.signalingState}.`); + } + } +} diff --git a/client/src/pointercorrect.js b/client/src/pointercorrect.js new file mode 100644 index 0000000..04e0f61 --- /dev/null +++ b/client/src/pointercorrect.js @@ -0,0 +1,124 @@ +export const LetterBoxType = { + Vertical: 0, + Horizontal: 1 +}; + +export class PointerCorrector { + /** + * @param {Number} videoWidth + * @param {Number} videoHeight + * @param {HTMLVideoElement} videoElem + */ + constructor(videoWidth, videoHeight, videoElem) { + this.reset(videoWidth, videoHeight, videoElem); + } + + /** + * @param {Number[]} position MouseEvent.clientX, MouseEvent.clientY + * @returns {Number[]} + */ + map(position) { + var rect = this._videoElem.getBoundingClientRect(); + const _position = new Array(2); + + // (1) set origin point to zero + _position[0] = position[0] - rect.left; + _position[1] = position[1] - rect.top; + + // (2) translate Unity coordinate system (reverse y-axis) + _position[1] = rect.height - _position[1]; + + // (3) add offset of letterbox + _position[0] -= this._contentRect.x; + _position[1] -= this._contentRect.y; + + // (4) mapping element rectangle to video rectangle + _position[0] = _position[0] / this._contentRect.width * this._videoWidth; + _position[1] = _position[1] / this._contentRect.height * this._videoHeight; + + return _position; + } + + /** + * @param {Number} videoWidth + */ + setVideoWidth(videoWidth) { + this._videoWidth = videoWidth; + this._reset(); + } + + /** + * @param {Number} videoHeight + */ + setVideoHeight(videoHeight) { + this._videoHeight = videoHeight; + this._reset(); + } + + /** + * @param {HTMLVideoElement} videoElem + */ + setRect(videoElem) { + this._videoElem = videoElem; + this._reset(); + } + + /** + * @param {Number} videoWidth + * @param {Number} videoHeight + * @param {HTMLVideoElement} videoElem + */ + reset(videoWidth, videoHeight, videoElem) { + this._videoWidth = videoWidth; + this._videoHeight = videoHeight; + this._videoElem = videoElem; + this._reset(); + } + + get letterBoxType() { + const videoRatio = this._videoHeight / this._videoWidth; + var rect = this._videoElem.getBoundingClientRect(); + const rectRatio = rect.height / rect.width; + return videoRatio > rectRatio ? LetterBoxType.Vertical : LetterBoxType.Horizontal; + } + + get letterBoxSize() { + var rect = this._videoElem.getBoundingClientRect(); + switch(this.letterBoxType) { + case LetterBoxType.Horizontal: { + const ratioWidth = rect.width / this._videoWidth; + const height = this._videoHeight * ratioWidth; + return (rect.height - height) * 0.5; + } + case LetterBoxType.Vertical: { + const ratioHeight = rect.height / this._videoHeight; + const width = this._videoWidth * ratioHeight; + return (rect.width - width) * 0.5; + } + } + throw 'invalid status'; + } + + /** + * Returns rectangle for displaying video with the origin at the left-top of the element. + * Not considered applying CSS like `object-fit`. + * @returns {Object} + */ + get contentRect() { + const letterBoxType = this.letterBoxType; + const letterBoxSize = this.letterBoxSize; + + var rect = this._videoElem.getBoundingClientRect(); + + const x = letterBoxType == LetterBoxType.Vertical ? letterBoxSize : 0; + const y = letterBoxType == LetterBoxType.Horizontal ? letterBoxSize : 0; + const width = letterBoxType == LetterBoxType.Vertical ? rect.width - letterBoxSize * 2 : rect.width; + const height = letterBoxType == LetterBoxType.Horizontal ? rect.height - letterBoxSize * 2 : rect.height; + + return {x: x, y: y, width: width, height: height}; + } + + _reset() { + this._contentRect = this.contentRect; + } +} diff --git a/client/src/renderstreaming.js b/client/src/renderstreaming.js new file mode 100644 index 0000000..52cbef1 --- /dev/null +++ b/client/src/renderstreaming.js @@ -0,0 +1,317 @@ +import Peer from "./peer.js"; +import * as Logger from "./logger.js"; + +function uuid4() { + var temp_url = URL.createObjectURL(new Blob()); + var uuid = temp_url.toString(); + URL.revokeObjectURL(temp_url); + return uuid.split(/[:/]/g).pop().toLowerCase(); +} + +export class RenderStreaming { + constructor(signaling, config) { + this._peer = null; // participant端:单一peer + this._peers = new Map(); // host端:多peer Map (participantId → Peer) + this._connectionId = null; + this._participantId = null; // 自己的participantId + this._isHost = false; + this.onConnect = function (connectionId, data) { Logger.log(`Connect peer on ${connectionId}.`); }; + this.onDisconnect = function (connectionId) { Logger.log(`Disconnect peer on ${connectionId}.`); }; + this.onGotOffer = function (connectionId) { Logger.log(`On got Offer on ${connectionId}.`); }; + this.onGotAnswer = function (connectionId) { Logger.log(`On got Answer on ${connectionId}.`); }; + this.onTrackEvent = function (data) { Logger.log(`OnTrack event peer with data:${data}`); }; + this.onAddChannel = function (data) { Logger.log(`onAddChannel event peer with data:${data}`); }; + this.onMessage = function (data) { Logger.log(`On message: ${data}`); }; + this.onParticipantLeft = function (participantId) { Logger.log(`Participant left: ${participantId}.`); }; + this.onParticipantJoined = function (participantId) { Logger.log(`Participant joined: ${participantId}.`); }; + this.onNewPeer = function (participantId) { Logger.log(`New peer created for ${participantId}.`); }; + this._config = config; + this._signaling = signaling; + this._signaling.addEventListener('connect', this._onConnect.bind(this)); + this._signaling.addEventListener('disconnect', this._onDisconnect.bind(this)); + this._signaling.addEventListener('offer', this._onOffer.bind(this)); + this._signaling.addEventListener('answer', this._onAnswer.bind(this)); + this._signaling.addEventListener('candidate', this._onIceCandidate.bind(this)); + this._signaling.addEventListener('on-message', this._onMessage.bind(this)); + this._signaling.addEventListener('participant-left', this._onParticipantLeft.bind(this)); + this._signaling.addEventListener('participant-joined', this._onParticipantJoined.bind(this)); + } + + async _onConnect(e) { + const data = e.detail; + if (this._connectionId == data.connectionId) { + this._participantId = data.participantId; + this._isHost = data.role === 'host'; + + if (!this._isHost) { + // participant端:立即创建单一peer并开始协商 + this._preparePeerConnection(this._connectionId, data.polite, null); + } + // host端:不在connect时创建peer,等participant加入后再创建 + + this.onConnect(data.connectionId, data); + } + } + + async _onDisconnect(e) { + const data = e.detail; + if (this._connectionId == data.connectionId) { + this.onDisconnect(data.connectionId); + if (this._peer) { + this._peer.close(); + this._peer = null; + } + // 关闭所有host端peers + this._peers.forEach((peer, participantId) => { + peer.close(); + }); + this._peers.clear(); + } + } + + async _onOffer(e) { + const offer = e.detail; + const participantId = offer.participantId; + + if (this._isHost) { + // host端:为该participant创建或复用peer + let peer = this._peers.get(participantId); + if (!peer || (peer.pc && peer.pc.iceConnectionState === 'disconnected')) { + if (peer) peer.close(); + peer = this._preparePeerConnection(this._connectionId, offer.polite, participantId); + } + const desc = new RTCSessionDescription({ sdp: offer.sdp, type: "offer" }); + try { + await peer.onGotDescription(this._connectionId, desc); + } catch (error) { + Logger.warn(`Error on GotDescription for participant ${participantId}: ${error}`); + } + } else { + // participant端:使用单一peer + if (this._peer && this._peer.pc && this._peer.pc.iceConnectionState === 'disconnected') { + this._peer.close(); + this._peer = null; + } + if (!this._peer) { + this._preparePeerConnection(offer.connectionId, offer.polite, null); + } + const desc = new RTCSessionDescription({ sdp: offer.sdp, type: "offer" }); + try { + await this._peer.onGotDescription(offer.connectionId, desc); + } catch (error) { + Logger.warn(`Error on GotDescription: ${error}`); + } + } + } + + async _onAnswer(e) { + const answer = e.detail; + const participantId = answer.participantId; + const desc = new RTCSessionDescription({ sdp: answer.sdp, type: "answer" }); + + if (this._isHost && participantId) { + // host端:路由到对应participant的peer + const peer = this._peers.get(participantId); + if (peer) { + try { + await peer.onGotDescription(this._connectionId, desc); + } catch (error) { + Logger.warn(`Error on GotDescription answer for ${participantId}: ${error}`); + } + } + } else if (this._peer) { + // participant端 + try { + await this._peer.onGotDescription(answer.connectionId, desc); + } catch (error) { + Logger.warn(`Error on GotDescription answer: ${error}`); + } + } + } + + async _onIceCandidate(e) { + const candidate = e.detail; + const participantId = candidate.participantId; + const iceCandidate = new RTCIceCandidate({ candidate: candidate.candidate, sdpMid: candidate.sdpMid, sdpMLineIndex: candidate.sdpMLineIndex }); + + if (this._isHost && participantId) { + // host端:路由到对应participant的peer + const peer = this._peers.get(participantId); + if (peer) { + await peer.onGotCandidate(this._connectionId, iceCandidate); + } + } else if (this._peer) { + // participant端 + await this._peer.onGotCandidate(candidate.connectionId, iceCandidate); + } + } + + async _onMessage(e) { + const data = e.detail; + this.onMessage(data); + } + + async _onParticipantLeft(e) { + const data = e.detail; + const participantId = data.participantId; + Logger.log(`Participant left: ${participantId}`); + + // 关闭该participant的peer + if (this._peers.has(participantId)) { + const peer = this._peers.get(participantId); + peer.close(); + this._peers.delete(participantId); + } + + this.onParticipantLeft(participantId); + } + + async _onParticipantJoined(e) { + const data = e.detail; + const participantId = data.participantId; + Logger.log(`Participant joined: ${participantId}`); + + // host端:为新participant创建peer + if (this._isHost && !this._peers.has(participantId)) { + this._preparePeerConnection(this._connectionId, false, participantId); + } + + this.onParticipantJoined(participantId); + } + + async createConnection(connectionId) { + this._connectionId = connectionId ? connectionId : uuid4(); + await this._signaling.createConnection(this._connectionId); + } + + async deleteConnection() { + await this._signaling.deleteConnection(this._connectionId); + } + + _preparePeerConnection(connectionId, polite, participantId) { + // host端多peer模式:participantId标识目标participant + // participant端单peer模式:participantId为null + + const peer = new Peer(connectionId, polite, this._config); + + // 保存peer + if (participantId) { + if (this._peers.has(participantId)) { + const oldPeer = this._peers.get(participantId); + oldPeer.close(); + } + this._peers.set(participantId, peer); + } else { + if (this._peer) { + this._peer.close(); + } + this._peer = peer; + } + + // 事件处理:附加participantId用于路由 + peer.addEventListener('trackevent', (e) => { + const data = e.detail; + data.participantId = participantId; + this.onTrackEvent(data); + }); + + peer.addEventListener('adddatachannel', (e) => { + const data = e.detail; + this.onAddChannel(data); + }); + + peer.addEventListener('ongotoffer', (e) => { + const id = e.detail.connectionId; + this.onGotOffer(id); + }); + + peer.addEventListener('ongotanswer', (e) => { + const id = e.detail.connectionId; + this.onGotAnswer(id); + }); + + peer.addEventListener('sendoffer', (e) => { + const offer = e.detail; + this._signaling.sendOffer(offer.connectionId, offer.sdp, participantId); + }); + + peer.addEventListener('sendanswer', (e) => { + const answer = e.detail; + this._signaling.sendAnswer(answer.connectionId, answer.sdp, participantId); + }); + + peer.addEventListener('sendcandidate', (e) => { + const candidate = e.detail; + this._signaling.sendCandidate(candidate.connectionId, candidate.candidate, candidate.sdpMid, candidate.sdpMLineIndex, participantId); + }); + + this.onNewPeer(participantId || connectionId); + return peer; + } + + async getStats(participantId) { + if (this._isHost && participantId) { + const peer = this._peers.get(participantId); + return peer ? await peer.getStats(this._connectionId) : null; + } + return this._peer ? await this._peer.getStats(this._connectionId) : null; + } + + createDataChannel(label, participantId) { + if (this._isHost && participantId) { + const peer = this._peers.get(participantId); + return peer ? peer.createDataChannel(this._connectionId, label) : null; + } + return this._peer ? this._peer.createDataChannel(this._connectionId, label) : null; + } + + addTrack(track, participantId) { + if (this._isHost && participantId) { + const peer = this._peers.get(participantId); + return peer ? peer.addTrack(this._connectionId, track) : null; + } + return this._peer ? this._peer.addTrack(this._connectionId, track) : null; + } + + addTransceiver(trackOrKind, init, participantId) { + if (this._isHost && participantId) { + const peer = this._peers.get(participantId); + return peer ? peer.addTransceiver(this._connectionId, trackOrKind, init) : null; + } + return this._peer ? this._peer.addTransceiver(this._connectionId, trackOrKind, init) : null; + } + + getTransceivers(participantId) { + if (this._isHost && participantId) { + const peer = this._peers.get(participantId); + return peer ? peer.getTransceivers(this._connectionId) : null; + } + return this._peer ? this._peer.getTransceivers(this._connectionId) : null; + } + + sendMessage(message) { + if (this._signaling && this._connectionId) { + this._signaling.sendMessage(this._connectionId, message); + } + } + + async start() { + await this._signaling.start(); + } + + async stop() { + if (this._peer) { + this._peer.close(); + this._peer = null; + } + this._peers.forEach((peer) => { + peer.close(); + }); + this._peers.clear(); + + if (this._signaling) { + await this._signaling.stop(); + this._signaling = null; + } + } +} \ No newline at end of file diff --git a/client/src/sender.js b/client/src/sender.js new file mode 100644 index 0000000..f5fb66d --- /dev/null +++ b/client/src/sender.js @@ -0,0 +1,208 @@ +import { + Mouse, + Keyboard, + Gamepad, + Touchscreen, + StateEvent, + TextEvent +} from "./inputdevice.js"; + +import { LocalInputManager } from "./inputremoting.js"; +import { GamepadHandler } from "./gamepadhandler.js"; +import { PointerCorrector } from "./pointercorrect.js"; + +export class Sender extends LocalInputManager { + constructor(elem) { + super(); + this._devices = []; + this._elem = elem; + this._corrector = new PointerCorrector( + this._elem.videoWidth, + this._elem.videoHeight, + this._elem + ); + + //since line 27 cannot complete resize initialization but can only monitor div dimension changes, line 26 needs to be reserved + this._elem.addEventListener('resize', this._onResizeEvent.bind(this), false); + const observer = new ResizeObserver(this._onResizeEvent.bind(this)); + observer.observe(this._elem); + } + + addMouse() { + const descriptionMouse = { + m_InterfaceName: "RawInput", + m_DeviceClass: "Mouse", + m_Manufacturer: "", + m_Product: "", + m_Serial: "", + m_Version: "", + m_Capabilities: "" + }; + this.mouse = new Mouse("Mouse", "Mouse", 1, null, descriptionMouse); + this._devices.push(this.mouse); + + this._elem.addEventListener('click', this._onMouseEvent.bind(this), false); + this._elem.addEventListener('mousedown', this._onMouseEvent.bind(this), false); + this._elem.addEventListener('mouseup', this._onMouseEvent.bind(this), false); + this._elem.addEventListener('mousemove', this._onMouseEvent.bind(this), false); + this._elem.addEventListener('wheel', this._onWheelEvent.bind(this), false); + } + + addKeyboard() { + const descriptionKeyboard = { + m_InterfaceName: "RawInput", + m_DeviceClass: "Keyboard", + m_Manufacturer: "", + m_Product: "", + m_Serial: "", + m_Version: "", + m_Capabilities: "" + }; + this.keyboard = new Keyboard("Keyboard", "Keyboard", 2, null, descriptionKeyboard); + this._devices.push(this.keyboard); + + document.addEventListener('keyup', this._onKeyEvent.bind(this), false); + document.addEventListener('keydown', this._onKeyEvent.bind(this), false); + } + + addGamepad() { + const descriptionGamepad = { + m_InterfaceName: "RawInput", + m_DeviceClass: "Gamepad", + m_Manufacturer: "", + m_Product: "", + m_Serial: "", + m_Version: "", + m_Capabilities: "" + }; + this.gamepad = new Gamepad("Gamepad", "Gamepad", 3, null, descriptionGamepad); + this._devices.push(this.gamepad); + + window.addEventListener("gamepadconnected", this._onGamepadEvent.bind(this), false); + window.addEventListener("gamepaddisconnected", this._onGamepadEvent.bind(this), false); + this._gamepadHandler = new GamepadHandler(); + this._gamepadHandler.addEventListener("gamepadupdated", this._onGamepadEvent.bind(this), false); + } + + addTouchscreen() { + const descriptionTouch = { + m_InterfaceName: "RawInput", + m_DeviceClass: "Touch", + m_Manufacturer: "", + m_Product: "", + m_Serial: "", + m_Version: "", + m_Capabilities: "" + }; + this.touchscreen = new Touchscreen("Touchscreen", "Touchscreen", 4, null, descriptionTouch); + this._devices.push(this.touchscreen); + + this._elem.addEventListener('touchend', this._onTouchEvent.bind(this), false); + this._elem.addEventListener('touchstart', this._onTouchEvent.bind(this), false); + this._elem.addEventListener('touchcancel', this._onTouchEvent.bind(this), false); + this._elem.addEventListener('touchmove', this._onTouchEvent.bind(this), false); + this._elem.addEventListener('click', this._onTouchEvent.bind(this), false); + } + + /** + * @returns {InputDevice[]} + */ + get devices() { + return this._devices; + } + + _onResizeEvent() { + this._corrector.reset( + this._elem.videoWidth, + this._elem.videoHeight, + this._elem + ); + } + _onMouseEvent(event) { + this.mouse.queueEvent(event); + this.mouse.currentState.position = this._corrector.map(this.mouse.currentState.position); + this._queueStateEvent(this.mouse.currentState, this.mouse); + } + _onWheelEvent(event) { + this.mouse.queueEvent(event); + this._queueStateEvent(this.mouse.currentState, this.mouse); + } + _onKeyEvent(event) { + if(event.type == 'keydown') { + if(!event.repeat) { // StateEvent + this.keyboard.queueEvent(event); + this._queueStateEvent(this.keyboard.currentState, this.keyboard); + } + // TextEvent + this._queueTextEvent(this.keyboard, event); + } + else if(event.type == 'keyup') { + this.keyboard.queueEvent(event); + this._queueStateEvent(this.keyboard.currentState, this.keyboard); + } + } + _onTouchEvent(event) { + this.touchscreen.queueEvent(event, this.timeSinceStartup); + for(let touch of this.touchscreen.currentState.touchData) { + let clone = touch.copy(); + clone.position = this._corrector.map(clone.position); + this._queueStateEvent(clone, this.touchscreen); + } + } + _onGamepadEvent(event) { + switch(event.type) { + case 'gamepadconnected': { + this._gamepadHandler.addGamepad(event.gamepad); + break; + } + case 'gamepaddisconnected': { + this._gamepadHandler.removeGamepad(event.gamepad); + break; + } + case 'gamepadupdated': { + this.gamepad.queueEvent(event); + this._queueStateEvent(this.gamepad.currentState, this.gamepad); + break; + } + } + } + + _queueStateEvent(state, device) { + const stateEvent = + StateEvent.fromState(state, device.deviceId, this.timeSinceStartup); + const e = new CustomEvent( + 'event', {detail: { event: stateEvent, device: device}}); + super.onEvent.dispatchEvent(e); + } + _queueTextEvent(device, event) { + const textEvent = TextEvent.create(device.deviceId, event, this.timeSinceStartup); + const e = new CustomEvent( + 'event', {detail: { event: textEvent, device: device}}); + super.onEvent.dispatchEvent(e); + } + _queueDeviceChange(device, usage) { + const e = new CustomEvent( + 'changedeviceusage', {detail: { device: device, usage: usage }}); + super.onEvent.dispatchEvent(e); + } +} + +export class Observer { + /** + * + * @param {RTCDataChannel} channel + */ + constructor(channel) { + this.channel = channel; + } + /** + * + * @param {Message} message + */ + onNext(message) { + if(this.channel == null || this.channel.readyState != 'open') { + return; + } + this.channel.send(message.buffer); + } +} diff --git a/client/src/signaling.js b/client/src/signaling.js new file mode 100644 index 0000000..f989113 --- /dev/null +++ b/client/src/signaling.js @@ -0,0 +1,276 @@ +import * as Logger from "./logger.js"; + +export class Signaling extends EventTarget { + + constructor(interval = 1000) { + super(); + this.running = false; + this.interval = interval; + this.sleep = msec => new Promise(resolve => setTimeout(resolve, msec)); + } + + headers() { + if (this.sessionId !== undefined) { + return { 'Content-Type': 'application/json', 'Session-Id': this.sessionId }; + } + else { + return { 'Content-Type': 'application/json' }; + } + } + + url(method, parameter = '') { + let ret = location.origin + '/signaling'; + if (method) + ret += '/' + method; + if (parameter) + ret += '?' + parameter; + return ret; + } + + async start() { + if (this.running) { + return; + } + + this.running = true; + while (!this.sessionId) { + const createResponse = await fetch(this.url(''), { method: 'PUT', headers: this.headers() }); + const session = await createResponse.json(); + this.sessionId = session.sessionId; + + if (!this.sessionId) { + await this.sleep(this.interval); + } + } + + this.loopGetAll(); + } + + async loopGetAll() { + let lastTimeRequest = Date.now() - 30000; + while (this.running) { + const res = await this.getAll(lastTimeRequest); + const data = await res.json(); + lastTimeRequest = data.datetime ? data.datetime : Date.now(); + + const messages = data.messages; + + for (const msg of messages) { + switch (msg.type) { + case "connect": + break; + case "disconnect": + this.dispatchEvent(new CustomEvent('disconnect', { detail: msg })); + break; + case "offer": + this.dispatchEvent(new CustomEvent('offer', { detail: msg })); + break; + case "answer": + this.dispatchEvent(new CustomEvent('answer', { detail: msg })); + break; + case "candidate": + this.dispatchEvent(new CustomEvent('candidate', { detail: msg })); + break; + case "on-message": + this.dispatchEvent(new CustomEvent('on-message', { detail: msg.data })); + break; + default: + break; + } + } + await this.sleep(this.interval); + } + } + + async stop() { + this.running = false; + await fetch(this.url(''), { method: 'DELETE', headers: this.headers() }); + this.sessionId = null; + } + + async createConnection(connectionId) { + const data = { 'connectionId': connectionId }; + const res = await fetch(this.url('connection'), { method: 'PUT', headers: this.headers(), body: JSON.stringify(data) }); + const json = await res.json(); + Logger.log(`Signaling: HTTP create connection, connectionId: ${json.connectionId}, polite:${json.polite}`); + + this.dispatchEvent(new CustomEvent('connect', { detail: json })); + return json; + } + + async deleteConnection(connectionId) { + const data = { 'connectionId': connectionId }; + const res = await fetch(this.url('connection'), { method: 'DELETE', headers: this.headers(), body: JSON.stringify(data) }); + const json = await res.json(); + this.dispatchEvent(new CustomEvent('disconnect', { detail: json })); + return json; + } + + async sendOffer(connectionId, sdp) { + const data = { 'sdp': sdp, 'connectionId': connectionId }; + Logger.log('sendOffer:' + data); + await fetch(this.url('offer'), { method: 'POST', headers: this.headers(), body: JSON.stringify(data) }); + } + + async sendAnswer(connectionId, sdp) { + const data = { 'sdp': sdp, 'connectionId': connectionId }; + Logger.log('sendAnswer:' + data); + await fetch(this.url('answer'), { method: 'POST', headers: this.headers(), body: JSON.stringify(data) }); + } + + async sendCandidate(connectionId, candidate, sdpMid, sdpMLineIndex) { + const data = { + 'candidate': candidate, + 'sdpMLineIndex': sdpMLineIndex, + 'sdpMid': sdpMid, + 'connectionId': connectionId + }; + Logger.log('sendCandidate:' + data); + await fetch(this.url('candidate'), { method: 'POST', headers: this.headers(), body: JSON.stringify(data) }); + } + // 在 Signaling 类中添加 + async sendMessage(connectionId, message) { + const data = { + 'message': message, + 'connectionId': connectionId + }; + await fetch(this.url('on-message'), { method: 'POST', headers: this.headers(), body: JSON.stringify(data) }); + } + async getAll(fromTime = 0) { + return await fetch(this.url(``, `fromtime=${fromTime}`), { method: 'GET', headers: this.headers() }); + } +} + +export class WebSocketSignaling extends EventTarget { + + constructor(interval = 1000) { + super(); + this.interval = interval; + this.sleep = msec => new Promise(resolve => setTimeout(resolve, msec)); + + let websocketUrl; + if (location.protocol === "https:") { + websocketUrl = "wss://" + location.host; + } else { + websocketUrl = "ws://" + location.host; + } + + this.websocket = new WebSocket(websocketUrl); + this.connectionId = null; + + this.websocket.onopen = () => { + this.isWsOpen = true; + }; + + this.websocket.onclose = () => { + this.isWsOpen = false; + }; + + this.websocket.onmessage = (event) => { + const msg = JSON.parse(event.data); + if (!msg || !this) { + return; + } + + Logger.log(msg); + + switch (msg.type) { + case "connect": + this.dispatchEvent(new CustomEvent('connect', { detail: msg })); + break; + case "disconnect": + this.dispatchEvent(new CustomEvent('disconnect', { detail: msg })); + break; + case "offer": + this.dispatchEvent(new CustomEvent('offer', { detail: { connectionId: msg.from, sdp: msg.data.sdp, polite: msg.data.polite, participantId: msg.participantId } })); + break; + case "answer": + this.dispatchEvent(new CustomEvent('answer', { detail: { connectionId: msg.from, sdp: msg.data.sdp, participantId: msg.participantId } })); + break; + case "candidate": + this.dispatchEvent(new CustomEvent('candidate', { detail: { connectionId: msg.from, candidate: msg.data.candidate, sdpMLineIndex: msg.data.sdpMLineIndex, sdpMid: msg.data.sdpMid, participantId: msg.participantId } })); + break; + case "on-message": + // 将participantId附加到消息数据中,以便Host识别消息发送者 + if (msg.participantId) { + msg.data.participantId = msg.participantId; + } + this.dispatchEvent(new CustomEvent('on-message', { detail: msg.data })); + break; + case "participant-left": + this.dispatchEvent(new CustomEvent('participant-left', { detail: msg })); + break; + case "participant-joined": + this.dispatchEvent(new CustomEvent('participant-joined', { detail: msg })); + break; + case "broadcast": + this.dispatchEvent(new CustomEvent('on-message', { detail: msg.message })); + break; + default: + break; + } + }; + } + + async start() { + while (!this.isWsOpen) { + await this.sleep(100); + } + } + + async stop() { + this.websocket.close(); + while (this.isWsOpen) { + await this.sleep(100); + } + } + + createConnection(connectionId) { + const sendJson = JSON.stringify({ type: "connect", connectionId: connectionId }); + Logger.log(sendJson); + this.websocket.send(sendJson); + } + + deleteConnection(connectionId) { + const sendJson = JSON.stringify({ type: "disconnect", connectionId: connectionId }); + Logger.log(sendJson); + this.websocket.send(sendJson); + } + + sendOffer(connectionId, sdp, participantId) { + const data = { 'sdp': sdp, 'connectionId': connectionId }; + const sendJson = JSON.stringify({ type: "offer", from: connectionId, data: data, participantId: participantId || '' }); + Logger.log(sendJson); + this.websocket.send(sendJson); + } + + sendAnswer(connectionId, sdp, participantId) { + const data = { 'sdp': sdp, 'connectionId': connectionId }; + const sendJson = JSON.stringify({ type: "answer", from: connectionId, data: data, participantId: participantId || '' }); + Logger.log(sendJson); + this.websocket.send(sendJson); + } + + sendCandidate(connectionId, candidate, sdpMLineIndex, sdpMid, participantId) { + const data = { + 'candidate': candidate, + 'sdpMLineIndex': sdpMLineIndex, + 'sdpMid': sdpMid, + 'connectionId': connectionId + }; + const sendJson = JSON.stringify({ type: "candidate", from: connectionId, data: data, participantId: participantId || '' }); + Logger.log(sendJson); + this.websocket.send(sendJson); + } + // 在 WebSocketSignaling 类中添加 + sendMessage(connectionId, message) { + const data = { + 'message': message, + 'senderId': message.senderId, + 'connectionId': connectionId + }; + const sendJson = JSON.stringify({ type: "on-message", data: data }); + Logger.log(sendJson); + this.websocket.send(sendJson); + } +} diff --git a/client/src/touchflags.js b/client/src/touchflags.js new file mode 100644 index 0000000..154ea64 --- /dev/null +++ b/client/src/touchflags.js @@ -0,0 +1,7 @@ +export const TouchFlags = +{ + IndirectTouch: 1 << 0, + PrimaryTouch: 1 << 4, + Tap: 1 << 5, + OrphanedPrimaryTouch: 1 << 6, +}; diff --git a/client/src/touchphase.js b/client/src/touchphase.js new file mode 100644 index 0000000..e461dcd --- /dev/null +++ b/client/src/touchphase.js @@ -0,0 +1,8 @@ +export const TouchPhase = { + None: 0, + Began: 1, + Moved: 2, + Ended: 3, + Canceled: 4, + Stationary: 5 + }; diff --git a/client/test/domrect.js b/client/test/domrect.js new file mode 100644 index 0000000..2b78492 --- /dev/null +++ b/client/test/domrect.js @@ -0,0 +1,16 @@ +// mock class + +export class DOMRect { + constructor(x, y, width, height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + get left() { + return this.x; + } + get top() { + return this.y; + } +} \ No newline at end of file diff --git a/client/test/domvideoelement.js b/client/test/domvideoelement.js new file mode 100644 index 0000000..858f7d7 --- /dev/null +++ b/client/test/domvideoelement.js @@ -0,0 +1,11 @@ +// mock class + +export class DOMHTMLVideoElement { + constructor(rect) { + this.rect = rect; + } + + getBoundingClientRect() { + return this.rect; + } +} diff --git a/client/test/inputdevice.test.js b/client/test/inputdevice.test.js new file mode 100644 index 0000000..5d39ab0 --- /dev/null +++ b/client/test/inputdevice.test.js @@ -0,0 +1,172 @@ +import { + FourCC, + Mouse, + Keyboard, + Touchscreen, + Gamepad, + KeyboardState, + MouseState, + TouchscreenState, + GamepadState, + StateEvent, + InputEvent, + TextEvent + } from "../src/inputdevice.js"; + +describe(`FourCC`, () => { + test('toInt32', () => { + const number = new FourCC('A', 'A', 'A', 'A').toInt32(); + expect(number).toBe(0x41414141); + }); +}); + +describe(`MouseState`, () => { + describe(`with MouseEvent`, () => { + let event; + beforeEach(() => { + event = new MouseEvent('click', { buttons:1, clientX:0, clientY:0}); + }); + test('format', () => { + const format = new MouseState(event).format; + expect(format).toBe(0x4d4f5553); + }); + test('buffer', () => { + const state = new MouseState(event); + expect(state.buffer.byteLength).toBeGreaterThan(0); + }); + }); + describe(`with WheelEvent`, () => { + let event; + beforeEach(() => { + event = new WheelEvent('wheel', { deltaX:0, deltaY:0 }); + }); + test('format', () => { + const format = new MouseState(event).format; + expect(format).toBe(0x4d4f5553); + }); + test('buffer', () => { + const state = new MouseState(event); + expect(state.buffer.byteLength).toBeGreaterThan(0); + }); + }); +}); + +describe(`KeyboardState`, () => { + let event; + beforeEach(() => { + event = new KeyboardEvent('keydown', { code: 'KeyA' }); + }); + test('format', () => { + const format = new KeyboardState(event).format; + expect(format).toBe(0x4b455953); + }); + test('buffer', () => { + const state = new KeyboardState(event); + expect(state.buffer.byteLength).toBeGreaterThan(0); + }); +}); + +describe(`TouchscreenState`, () => { + let event; + beforeEach(() => { + event = new TouchEvent("touchstart", { + changedTouches: [{ // InputInit + identifier: 0, + target: null, + clientX: 0, + clientY: 0, + screenX: 0, + screenY: 0, + pageX: 0, + pageY: 0, + radiusX: 0, + radiusY: 0, + rotationAngle: 0, + force: 0, + altitudeAngle: 0, + azimuthAngle:0, + touchType: "direct" + }] + }); + }); + test('format', () => { + const format = new TouchscreenState(event, null, Date.now()).format; + expect(format).toBe(0x54534352); + }); + test('buffer', () => { + const state = new TouchscreenState(event, null, Date.now()); + expect(state.buffer.byteLength).toBeGreaterThan(0); + }); +}); + +describe(`GamepadState`, () => { + let event; + beforeEach(() => { + event = { + type: 'gamepadupdated', + gamepad : { + id: 1, + buttons: Array(16).fill({ pressed: false, value: 1 }), + axes:[0, 0, 0, 0] + }}; + }); + test('format', () => { + const format = new GamepadState(event).format; + expect(format).toBe(0x47504144); + }); + test('buffer', () => { + const state = new GamepadState(event); + expect(state.buffer.byteLength).toBeGreaterThan(0); + }); +}); + +describe(`StateEvent`, () => { + let state; + beforeEach(() => { + const event = new KeyboardEvent('keydown', { code: 'KeyA' }); + state = new KeyboardState(event); + }); + test('buffer', () => { + const stateEvent = StateEvent.fromState(state, 0, Date.now()); + expect(new Int32Array(stateEvent.buffer.slice(0, 4))[0]).toBe(StateEvent.format); + }); +}); + +describe(`TextEvent`, () => { + test('buffer', () => { + const event = new KeyboardEvent('keydown', { code: 'KeyA', key: "a"}); + const textEvent = TextEvent.create(0, event, Date.now()); + expect(new Int32Array(textEvent.buffer.slice(0, 4))[0]).toBe(TextEvent.format); + const offset = InputEvent.size; + // 'a' is 97 + expect(new Uint32Array(textEvent.buffer.slice(offset, offset+4))[0]).toBe(97); + }); +}); + +describe(`Mouse`, () => { + test('alignedSizeInBytes', () => { + let device = new Mouse("Mouse", "Mouse", 1, null, null); + expect(device).toBeInstanceOf(Mouse); + }); +}); + +describe(`Keyboard`, () => { + test('alignedSizeInBytes', () => { + let device = new Keyboard("Keyboard", "Keyboard", 1, null, null); + expect(device).toBeInstanceOf(Keyboard); + }); +}); + +describe(`Touchscreen`, () => { + test('alignedSizeInBytes', () => { + let device = new Touchscreen("Touchscreen", "Touchscreen", 1, null, null); + expect(device).toBeInstanceOf(Touchscreen); + }); +}); + +describe(`Gamepad`, () => { + test('alignedSizeInBytes', () => { + let device = new Gamepad("Gamepad", "Gamepad", 1, null, null); + expect(device).toBeInstanceOf(Gamepad); + }); +}); diff --git a/client/test/inputremoting.test.js b/client/test/inputremoting.test.js new file mode 100644 index 0000000..abcee80 --- /dev/null +++ b/client/test/inputremoting.test.js @@ -0,0 +1,132 @@ +import { + InputDevice, + MouseState, + KeyboardState, + TouchscreenState, + GamepadState +} from "../src/inputdevice.js"; + +import { + MessageType, + NewDeviceMsg, + NewEventsMsg, + RemoveDeviceMsg, + InputRemoting, +} from "../src/inputremoting.js"; + +import { + Sender, + Observer +} from "../src/sender.js"; + +import {DOMRect} from "./domrect.js"; + +describe(`InputRemoting`, () => { + let sender = null; + let inputRemoting = null; + let observer = null; + beforeEach(async () => { + document.getBoundingClientRect = function(){ return new DOMRect(0,0,0,0); }; + sender = new Sender(document); + inputRemoting = new InputRemoting(sender); + let dc = null; + observer = new Observer(dc); + }); + test('startSending', () => { + expect.assertions(0); + inputRemoting.startSending(); + }); + test('stopSending', () => { + expect.assertions(0); + inputRemoting.startSending(); + inputRemoting.stopSending(); + }); + test('subscribe', () => { + expect.assertions(0); + inputRemoting.subscribe(observer); + }); +}); + +test('create NewDeviceMsg', () => { + const device = new InputDevice("Keyboard", "Keyboard", 0, null, null); + const msg = NewDeviceMsg.create(device); + expect(msg.participant_id).toBe(0); + expect(msg.type).toBe(MessageType.NewDevice); + expect(msg.data).toBeInstanceOf(ArrayBuffer); + expect(msg.data.byteLength).toBeGreaterThan(0); +}); + +describe('create NewEventMsg', () => { + test('using MouseState', () => { + const event = new MouseEvent('click', { buttons:0, clientX:0, clientY:0} ); + const state = new MouseState(event); + const msg = NewEventsMsg.create(state); + expect(msg.participant_id).toBe(0); + expect(msg.type).toBe(MessageType.NewEvents); + expect(msg.data).toBeInstanceOf(ArrayBuffer); + expect(msg.data.byteLength).toBeGreaterThan(0); + }); + test('using KeyboardState', () => { + const event = new KeyboardEvent("keydown", { code: 'KeyA' }); + const state = new KeyboardState(event); + const msg = NewEventsMsg.create(state); + expect(msg.participant_id).toBe(0); + expect(msg.type).toBe(MessageType.NewEvents); + expect(msg.data).toBeInstanceOf(ArrayBuffer); + expect(msg.data.byteLength).toBeGreaterThan(0); + }); + test('using TouchscreenState', () => { + const event = new TouchEvent("touchstart", { + changedTouches: [{ // InputInit + identifier: 0, + target: null, + clientX: 0, + clientY: 0, + screenX: 0, + screenY: 0, + pageX: 0, + pageY: 0, + radiusX: 0, + radiusY: 0, + rotationAngle: 0, + force: 0, + altitudeAngle: 0, + azimuthAngle:0, + touchType: "direct" + }] + }); + const state = new TouchscreenState(event, null, Date.now()); + expect(state.touchData).not.toBeNull(); + expect(state.touchData).toHaveLength(1); + const msg = NewEventsMsg.create(state.touchData[0]); + expect(msg.participant_id).toBe(0); + expect(msg.type).toBe(MessageType.NewEvents); + expect(msg.data).toBeInstanceOf(ArrayBuffer); + expect(msg.data.byteLength).toBeGreaterThan(0); + }); + test('using GamepadState', () => { + const event = { + type: 'gamepadupdated', + gamepad : { + id: 1, + buttons: Array(16).fill({ pressed: false, value: 1 }), + axes:[1, 1, 1, 1] + }}; + const state = new GamepadState(event); + const msg = NewEventsMsg.create(state); + expect(msg.participant_id).toBe(0); + expect(msg.type).toBe(MessageType.NewEvents); + expect(msg.data).toBeInstanceOf(ArrayBuffer); + expect(msg.data.byteLength).toBeGreaterThan(0); + }); +}); + +test('create RemoveDeviceMsg', () => { + const device = new InputDevice("Keyboard", "Keyboard", 0, null, null); + const msg = RemoveDeviceMsg.create(device); + expect(msg.participant_id).toBe(0); + expect(msg.type).toBe(MessageType.RemoveDevice); + expect(msg.data).toBeInstanceOf(ArrayBuffer); + expect(msg.data.byteLength).toBeGreaterThan(0); +}); + \ No newline at end of file diff --git a/client/test/memoryhelper.test.js b/client/test/memoryhelper.test.js new file mode 100644 index 0000000..a45ccd7 --- /dev/null +++ b/client/test/memoryhelper.test.js @@ -0,0 +1,67 @@ +import { + MemoryHelper +} from "../src/memoryhelper.js"; + +describe(`MemoryHelper.writeSingleBit`, () => { + test('turn on with offset 0', () => { + let bytes = new ArrayBuffer(3); + MemoryHelper.writeSingleBit(bytes, 0, false); + + // check 00 00 00 + const view = new Uint8Array(bytes); + expect(view[0]).toBe(0); + expect(view[1]).toBe(0); + expect(view[2]).toBe(0); + }); + test('turn off with offset 0', () => { + let bytes = new ArrayBuffer(3); + MemoryHelper.writeSingleBit(bytes, 0, true); + + // check 00 00 01 + const view = new Uint8Array(bytes); + expect(view[0]).toBe(1); + expect(view[1]).toBe(0); + expect(view[2]).toBe(0); + + MemoryHelper.writeSingleBit(bytes, 0, false); + + // check 00 00 00 + expect(view[0]).toBe(0); + expect(view[1]).toBe(0); + expect(view[2]).toBe(0); + }); + test('turn on with offset 32', () => { + let bytes = new ArrayBuffer(3); + MemoryHelper.writeSingleBit(bytes, 8, true); + + // check 00 01 00 + const view = new Uint8Array(bytes); + expect(view[0]).toBe(0); + expect(view[1]).toBe(1); + expect(view[2]).toBe(0); + + MemoryHelper.writeSingleBit(bytes, 0, true); + + // check 00 01 01 + expect(view[0]).toBe(1); + expect(view[1]).toBe(1); + expect(view[2]).toBe(0); + }); + test('turn on with offset 15', () => { + let bytes = new ArrayBuffer(3); + MemoryHelper.writeSingleBit(bytes, 15, true); + + // check 00 80 00 + const view = new Uint8Array(bytes); + expect(view[0]).toBe(0); + expect(view[1]).toBe(128); + expect(view[2]).toBe(0); + + MemoryHelper.writeSingleBit(bytes, 15, false); + + // check 00 00 00 + expect(view[0]).toBe(0); + expect(view[1]).toBe(0); + expect(view[2]).toBe(0); + }); +}); \ No newline at end of file diff --git a/client/test/mocksignaling.js b/client/test/mocksignaling.js new file mode 100644 index 0000000..e82d66c --- /dev/null +++ b/client/test/mocksignaling.js @@ -0,0 +1,224 @@ +import { sleep } from "./testutils"; + +/** @type {MockPrivateSignalingManager | MockPublicSignalingManager} */ +let manager; + +export function reset(isPrivate) { + manager = isPrivate ? new MockPrivateSignalingManager() : new MockPublicSignalingManager(); +} + +export class MockSignaling extends EventTarget { + + constructor(interval = 1000) { + super(); + this.interval = interval; + } + + async start() { + await manager.add(this); + } + + async stop() { + await manager.remove(this); + } + + async createConnection(connectionId) { + await manager.openConnection(this, connectionId); + } + + async deleteConnection(connectionId) { + await manager.closeConnection(this, connectionId); + } + + async sendOffer(connectionId, sdp) { + const data = { 'sdp': sdp, 'connectionId': connectionId }; + await manager.offer(this, data); + } + + async sendAnswer(connectionId, sdp) { + const data = { 'sdp': sdp, 'connectionId': connectionId }; + await manager.answer(this, data); + } + + async sendCandidate(connectionId, candidate, sdpMLineIndex, sdpMid) { + const data = { + 'candidate': candidate, + 'sdpMLineIndex': sdpMLineIndex, + 'sdpMid': sdpMid, + 'connectionId': connectionId + }; + await manager.candidate(this, data); + } +} + +class MockPublicSignalingManager { + constructor() { + this.list = new Set(); + this.delay = async () => await sleep(10); + } + + async add(signaling) { + await this.delay(); + this.list.add(signaling); + signaling.dispatchEvent(new Event("start")); + } + + async remove(signaling) { + await this.delay(); + this.list.delete(signaling); + signaling.dispatchEvent(new Event("end")); + } + + async openConnection(signaling, connectionId) { + await this.delay(); + const data = { connectionId: connectionId, polite: true }; + signaling.dispatchEvent(new CustomEvent("connect", { detail: data })); + } + + async closeConnection(signaling, connectionId) { + await this.delay(); + const data = { connectionId: connectionId }; + for (const element of this.list) { + element.dispatchEvent(new CustomEvent("disconnect", { detail: data })); + } + } + + async offer(owner, data) { + await this.delay(); + data.polite = false; + for (const signaling of this.list) { + if (signaling != owner) { + signaling.dispatchEvent(new CustomEvent("offer", { detail: data })); + } + } + } + + async answer(owner, data) { + await this.delay(); + for (const signaling of this.list) { + if (signaling != owner) { + signaling.dispatchEvent(new CustomEvent("answer", { detail: data })); + } + } + } + + async candidate(owner, data) { + await this.delay(); + for (const signaling of this.list) { + if (signaling != owner) { + signaling.dispatchEvent(new CustomEvent("candidate", { detail: data })); + } + } + } +} + +class MockPrivateSignalingManager { + constructor() { + // structure Map> connectionIds + this.connectionIds = new Map(); + this.delay = async () => await sleep(10); + } + + async add(signaling) { + await this.delay(); + signaling.dispatchEvent(new Event("start")); + } + + async remove(signaling) { + await this.delay(); + signaling.dispatchEvent(new Event("end")); + } + + async openConnection(signaling, connectionId) { + await this.delay(); + const peerExists = this.connectionIds.has(connectionId); + if (!peerExists) { + this.connectionIds.set(connectionId, new Set()); + } + + const list = this.connectionIds.get(connectionId); + list.add(signaling); + + const data = { connectionId: connectionId, polite: peerExists }; + signaling.dispatchEvent(new CustomEvent("connect", { detail: data })); + } + + async closeConnection(signaling, connectionId) { + await this.delay(); + const peerExists = this.connectionIds.has(connectionId); + const list = this.connectionIds.get(connectionId); + if (!peerExists || !list.has(signaling)) { + console.error(`${connectionId} This connection id is not used.`); + } + + const data = { connectionId: connectionId }; + for (const element of list) { + element.dispatchEvent(new CustomEvent("disconnect", { detail: data })); + } + + list.delete(signaling); + if (list.size == 0) { + this.connectionIds.delete(connectionId); + } + } + + findList(owner, connectionId) { + if (!this.connectionIds.has(connectionId)) { + return null; + } + + const list = new Set(this.connectionIds.get(connectionId)); + list.delete(owner); + if (list.Count == 0) { + return null; + } + + return list; + } + + async offer(owner, data) { + await this.delay(); + const list = this.findList(owner, data.connectionId); + if (list == null) { + console.warn(`${data.connectionId} This connection id is not ready other session.`); + return; + } + + data.polite = true; + for (const signaling of list) { + if (signaling != owner) { + signaling.dispatchEvent(new CustomEvent("offer", { detail: data })); + } + } + } + + async answer(owner, data) { + await this.delay(); + const list = this.findList(owner, data.connectionId); + if (list == null) { + console.warn(`${data.connectionId} This connection id is not ready other session.`); + return; + } + + for (const signaling of list) { + if (signaling != owner) { + signaling.dispatchEvent(new CustomEvent("answer", { detail: data })); + } + } + } + + async candidate(owner, data) { + await this.delay(); + const list = this.findList(owner, data.connectionId); + if (list == null) { + console.warn(`${data.connectionId} This connection id is not ready other session.`); + return; + } + + for (const signaling of list) { + if (signaling != owner) { + signaling.dispatchEvent(new CustomEvent("candidate", { detail: data })); + } + } + } +} diff --git a/client/test/peerconnection.test.js b/client/test/peerconnection.test.js new file mode 100644 index 0000000..78fb90a --- /dev/null +++ b/client/test/peerconnection.test.js @@ -0,0 +1,250 @@ +import Peer from "../src/peer.js"; +import { waitFor, sleep, getUniqueId, getRTCConfiguration } from "./testutils.js"; + + +describe(`peer connection test`, () => { + const connectionId = "12345"; + + test(`constructor`, () => { + const peer = new Peer(connectionId, true); + expect(peer).not.toBeNull(); + + const rtcPeer = peer.pc; + expect(rtcPeer).not.toBeNull(); + expect(rtcPeer.ontrack).not.toBeNull(); + expect(rtcPeer.onicecandidate).not.toBeNull(); + expect(rtcPeer.onnegotiationneeded).not.toBeNull(); + expect(rtcPeer.onsignalingstatechange).not.toBeNull(); + expect(rtcPeer.oniceconnectionstatechange).not.toBeNull(); + expect(rtcPeer.onicegatheringstatechange).not.toBeNull(); + }); + + test(`close peer`, async () => { + const config = getRTCConfiguration(); + const peer = new Peer(connectionId, true, config); + expect(peer).not.toBeNull(); + + peer.close(); + expect(peer.connectionId).toBeNull(); + expect(peer.pc).toBeNull(); + }); + + test(`transceiver direction is sendrecv if using addtrack`, () => { + const config = getRTCConfiguration(); + const peer = new Peer(connectionId, true, config); + expect(peer).not.toBeNull(); + + const track = { id: getUniqueId(), kind: "audio" }; + const sender = peer.addTrack(connectionId, track); + const transceiver = peer.getTransceivers(connectionId).find(t => t.sender == sender); + expect(transceiver.direction).toBe("sendrecv"); + }); + + test(`fire trackevent when addtrack`, async () => { + let trackEvent; + const config = getRTCConfiguration(); + const peer = new Peer(connectionId, true, config); + expect(peer).not.toBeNull(); + peer.addEventListener('trackevent', (e) => trackEvent = e.detail); + + const track = { id: getUniqueId(), kind: "audio" }; + peer.addTrack(connectionId, track); + await waitFor(() => trackEvent != null); + expect(trackEvent.track).toBe(track); + }); + + test(`fire trackevent when on got offer description include track`, async () => { + let trackEvent; + const config = getRTCConfiguration(); + const peer = new Peer(connectionId, true, config); + expect(peer).not.toBeNull(); + peer.addEventListener('trackevent', (e) => trackEvent = e.detail); + + const testDesc = { type: "offer", sdp: "newtracksdp" }; + peer.onGotDescription(connectionId, testDesc); + await waitFor(() => trackEvent != null); + expect(trackEvent.track).not.toBeNull(); + }); + + test(`fire sendoffer when addtrack`, async () => { + let offer; + const config = getRTCConfiguration(); + const peer = new Peer(connectionId, true, config); + expect(peer).not.toBeNull(); + peer.addEventListener('sendoffer', (e) => offer = e.detail); + + const track = { id: getUniqueId(), kind: "audio" }; + peer.addTrack(connectionId, track); + await waitFor(() => offer != null); + expect(offer.connectionId).toBe(connectionId); + }); + + test(`fire sendoffer when addTransceiver`, async () => { + let offer; + const config = getRTCConfiguration(); + const peer = new Peer(connectionId, true, config); + expect(peer).not.toBeNull(); + peer.addEventListener('sendoffer', (e) => offer = e.detail); + + peer.addTransceiver(connectionId, "video"); + await waitFor(() => offer != null); + expect(offer.connectionId).toBe(connectionId); + }); + + test(`fire sendoffer when createDataChannel`, async () => { + let offer; + const config = getRTCConfiguration(); + const peer = new Peer(connectionId, true, config); + expect(peer).not.toBeNull(); + peer.addEventListener('sendoffer', (e) => offer = e.detail); + + peer.createDataChannel(connectionId, "testChannel"); + await waitFor(() => offer != null); + expect(offer.connectionId).toBe(connectionId); + }); + + test(`re-fire sendoffer if get answer not yet`, async () => { + let sendOfferCount = 0; + let offer; + const config = getRTCConfiguration(); + const peer = new Peer(connectionId, true, config, 100); + expect(peer).not.toBeNull(); + peer.addEventListener('sendoffer', (e) => { + offer = e.detail; + sendOfferCount++; + }); + + const track = { id: getUniqueId(), kind: "audio" }; + peer.addTrack(connectionId, track); + await waitFor(() => sendOfferCount > 2); + expect(offer.connectionId).toBe(connectionId); + expect(sendOfferCount).toBeGreaterThan(2); + }); + + test(`fire sendanswer when on got offer description in polite`, async () => { + let answer; + const config = getRTCConfiguration(); + const peer = new Peer(connectionId, true, config); + expect(peer).not.toBeNull(); + peer.addEventListener('sendanswer', (e) => answer = e.detail); + + const testDesc = { type: "offer", sdp: "newtracksdp" }; + peer.onGotDescription(connectionId, testDesc); + await waitFor(() => answer != null); + expect(answer.connectionId).toBe(connectionId); + }); + + test(`fire sendanswer when on got offer description in polite that have offer`, async () => { + let offer; + let answer; + const config = getRTCConfiguration(); + const peer = new Peer(connectionId, true, config); + expect(peer).not.toBeNull(); + peer.addEventListener('sendoffer', (e) => offer = e.detail); + peer.addEventListener('sendanswer', (e) => answer = e.detail); + + const track = { id: getUniqueId(), kind: "audio" }; + peer.addTrack(connectionId, track); + await waitFor(() => offer != null); + expect(offer.connectionId).toBe(connectionId); + + const testDesc = { type: "offer", sdp: "newtracksdp" }; + peer.onGotDescription(connectionId, testDesc); + await waitFor(() => answer != null); + expect(answer.connectionId).toBe(connectionId); + }); + + test(`fire sendanswer when on got offer description in impolite that don't have offer`, async () => { + let answer; + const config = getRTCConfiguration(); + const peer = new Peer(connectionId, false, config); + expect(peer).not.toBeNull(); + peer.addEventListener('sendanswer', (e) => answer = e.detail); + + const testDesc = { type: "offer", sdp: "newtracksdp" }; + peer.onGotDescription(connectionId, testDesc); + await waitFor(() => answer != null); + expect(answer.connectionId).toBe(connectionId); + }); + + test(`don't fire sendanswer when on got offer description in impolite that have offer`, async () => { + let offer; + let answer; + const config = getRTCConfiguration(); + const peer = new Peer(connectionId, false, config); + expect(peer).not.toBeNull(); + peer.addEventListener('sendoffer', (e) => offer = e.detail); + peer.addEventListener('sendanswer', (e) => answer = e.detail); + + const track = { id: getUniqueId(), kind: "audio" }; + peer.addTrack(connectionId, track); + await waitFor(() => offer != null); + expect(offer.connectionId).toBe(connectionId); + + const testDesc = { type: "offer", sdp: "newtracksdp" }; + peer.onGotDescription(connectionId, testDesc); + await sleep(100); + expect(answer).toBeUndefined(); + }); + + test(`fire nagotiated when on got answer description that have offer`, async () => { + let offer; + let negotiated = false; + const config = getRTCConfiguration(); + const peer = new Peer(connectionId, true, config); + expect(peer).not.toBeNull(); + peer.addEventListener('sendoffer', (e) => offer = e.detail); + peer.pc.addEventListener('negotiated', () => negotiated = true); + + const track = { id: getUniqueId(), kind: "audio" }; + peer.addTrack(connectionId, track); + await waitFor(() => offer != null); + expect(offer.connectionId).toBe(connectionId); + + const answerDesc = { type: "answer", sdp: "newtracksdp" }; + peer.onGotDescription(connectionId, answerDesc); + await waitFor(() => negotiated); + expect(negotiated).toBeTruthy(); + }); + + test(`fire sendcandidate when on addTransceiver`, async () => { + let candidate; + const config = getRTCConfiguration(); + const peer = new Peer(connectionId, true, config); + expect(peer).not.toBeNull(); + peer.addEventListener('sendcandidate', (e) => candidate = e.detail); + + peer.addTransceiver(connectionId, { id: getUniqueId(), kind: "video" }); + await waitFor(() => candidate != null); + expect(candidate.connectionId).toBe(connectionId); + }); + + test(`accept candidate when on got candidate that have remote description`, async () => { + let answer; + const config = getRTCConfiguration(); + const peer = new Peer(connectionId, false, config); + expect(peer).not.toBeNull(); + peer.addEventListener('sendanswer', (e) => answer = e.detail); + + const testDesc = { type: "offer", sdp: "newtracksdp" }; + peer.onGotDescription(connectionId, testDesc); + await waitFor(() => answer != null); + expect(answer.connectionId).toBe(connectionId); + + const testCandidate = { candidate: getUniqueId(), sdpMLineIndex: 0, sdpMid: 0 }; + peer.onGotCandidate(connectionId, testCandidate); + await waitFor(() => peer.pc.candidates.length > 0); + expect(peer.pc.candidates.length).toBeGreaterThan(0); + }); + + test(`don't accept candidate when on got candidate that don't have remote description`, async () => { + const config = getRTCConfiguration(); + const peer = new Peer(connectionId, false, config); + expect(peer).not.toBeNull(); + + const testCandidate = { candidate: getUniqueId(), sdpMLineIndex: 0, sdpMid: 0 }; + peer.onGotCandidate(connectionId, testCandidate); + await sleep(100); + expect(peer.pc.candidates.length).toBe(0); + }); +}); diff --git a/client/test/peerconnectionmock.js b/client/test/peerconnectionmock.js new file mode 100644 index 0000000..96d1cd3 --- /dev/null +++ b/client/test/peerconnectionmock.js @@ -0,0 +1,316 @@ +import { sleep, getUniqueId } from './testutils'; + +export class PeerConnectionMock extends EventTarget { + constructor(config) { + super(); + this.delay = async () => await sleep(10); + this.config = config; + this.ontrack = undefined; + this.ondatachannel = undefined; + this.onicecandidate = undefined; + this.onnegotiationneeded = undefined; + this.onsignalingstatechange = undefined; + this.oniceconnectionstatechange = undefined; + this.onicegatheringstatechange = undefined; + this.pendingLocalDescription = null; + this.currentLocalDescription = null; + this.pendingRemoteDescription = null; + this.currentRemoteDescription = null; + this.candidates = []; + this.signalingState = "stable"; + this.iceConnectionState = "new"; + this.iceGatheringState = "new"; + this.audioTracks = new Map(); + this.videoTracks = new Map(); + this.channels = new Map(); + this.transceiverCount = 0; + this.transceivers = new Map(); + } + + get localDescription() { + if (this.pendingLocalDescription) { + return this.pendingLocalDescription; + } + + return this.currentLocalDescription; + } + + get remoteDescription() { + if (this.pendingRemoteDescription) { + return this.pendingRemoteDescription; + } + + return this.currentRemoteDescription; + } + + close() { + this.ontrack = undefined; + this.ondatachannel = undefined; + this.onicecandidate = undefined; + this.onnegotiationneeded = undefined; + this.onsignalingstatechange = undefined; + this.oniceconnectionstatechange = undefined; + this.onicegatheringstatechange = undefined; + this.pendingLocalDescription = null; + this.currentLocalDescription = null; + this.pendingRemoteDescription = null; + this.currentRemoteDescription = null; + this.candidates = []; + this.signalingState = "close"; + this.iceConnectionState = "closed"; + this.audioTracks.clear(); + this.videoTracks.clear(); + this.channels.clear(); + this.transceiverCount = 0; + this.transceivers.clear(); + } + + fireOnNegotiationNeeded() { + if (this.onnegotiationneeded) { + this.onnegotiationneeded(); + } + } + + getTransceivers() { + return Array.from(this.transceivers.values()); + } + + addTrack(track) { + if (track.kind == "audio") { + this.audioTracks.set(track.id, track); + } else { + this.videoTracks.set(track.id, track); + } + const transceiver = { direction: "sendrecv", sender: { track: track }, receiver: null, setCodecPreferences: (codecs) => { console.log(codecs); } }; + this.transceivers.set(this.transceiverCount++, transceiver); + this.fireOnNegotiationNeeded(); + return transceiver.sender; + } + + addTransceiver(trackOrKind) { + if (typeof trackOrKind == "string") { + const track = { id: getUniqueId(), kind: trackOrKind }; + if (track.kind == "audio") { + this.audioTracks.set(track.id, track); + } else { + this.videoTracks.set(track.id, track); + } + const transceiver = { direction: "sendrecv", sender: { track: track }, receiver: null, setCodecPreferences: (codecs) => { console.log(codecs); } }; + this.transceivers.set(this.transceiverCount++, transceiver); + this.fireOnNegotiationNeeded(); + return transceiver; + } + + if (trackOrKind.kind == "audio") { + this.audioTracks.set(trackOrKind.id, trackOrKind); + } else { + this.videoTracks.set(trackOrKind.id, trackOrKind); + } + const transceiver = { direction: "sendrecv", sender: { track: trackOrKind }, receiver: null, setCodecPreferences: (codecs) => { console.log(codecs); } }; + this.transceivers.set(this.transceiverCount++, transceiver); + this.fireOnNegotiationNeeded(); + return transceiver; + } + + createDataChannel(label) { + const channel = { id: getUniqueId(), label: label }; + this.channels.set(channel.id, channel); + this.fireOnNegotiationNeeded(); + return channel; + } + + async setLocalDescription(description = null) { + if (description == null) { + description = this._createSessionDescription(); + } + await this.delay(); + this._setSessionDescription(description, false); + } + + async setRemoteDescription(description) { + await this.delay(); + if (description.type == "offer" && this.signalingState == "have-local-offer") { + this._setSessionDescription({ type: "rollback", sdp: "" }, true); + } + this._setSessionDescription(description, true); + } + + _createSessionDescription() { + let dummySdp = "testsdp"; + if (this.videoTracks.size > 0) { + dummySdp += "videotrack"; + } + if (this.audioTracks.size > 0) { + dummySdp += "audiotrack"; + } + if (this.channels.size > 0) { + dummySdp += "datachannel"; + } + + if (this.signalingState == "stable" || this.signalingState == "have-local-offer" || this.signalingState == "have-remote-pranswer") { + return { type: "offer", sdp: dummySdp }; + } + return { type: "answer", sdp: dummySdp }; + } + + _setSessionDescription(description, remote) { + if (description.type == "rollback" + && (this.signalingState == "stable" || this.signalingState == "have-local-pranswer" || this.signalingState == "have-remote-pranswer")) { + throw "InvalidStateError"; + } + + if (description.type != "rollback") { + if (remote) { + if (description.type == "offer") { + this.pendingRemoteDescription = description; + this.signalingState = "have-remote-offer"; + this.onsignalingstatechange(this.signalingState); + // if sdp contains track string, create dummy track + if (description.sdp.includes("track")) { + const isVideo = description.sdp.includes("video"); + const kind = isVideo ? "video" : "audio"; + this._createTrackAndTransceiver(kind); + } + if (description.sdp.includes("datachannel")) { + const channel = { id: getUniqueId(), label: "dummychannel" }; + this.channels.set(channel.id, channel); + } + } + if (description.type == "answer") { + this.currentRemoteDescription = description; + this.currentLocalDescription = this.pendingLocalDescription; + this.pendingLocalDescription = null; + this.pendingRemoteDescription = null; + this.signalingState = "stable"; + this.onsignalingstatechange(this.signalingState); + } + if (description.type == "pranswer") { + this.pendingRemoteDescription = description; + this.signalingState = "have-remote-pranswer"; + this.onsignalingstatechange(this.signalingState); + } + } else { + if (description.type == "offer") { + this.pendingLocalDescription = description; + this.signalingState = "have-local-offer"; + this.onsignalingstatechange(this.signalingState); + } + if (description.type == "answer") { + this.currentLocalDescription = description; + this.currentRemoteDescription = this.pendingRemoteDescription; + this.pendingLocalDescription = null; + this.pendingRemoteDescription = null; + this.signalingState = "stable"; + this.onsignalingstatechange(this.signalingState); + // if sdp contains track string, create dummy track + if (description.sdp.includes("track")) { + const isVideo = description.sdp.includes("video"); + const kind = isVideo ? "video" : "audio"; + this._createTrackAndTransceiver(kind); + } + if (description.sdp.includes("datachannel")) { + const channel = { id: getUniqueId(), label: "dummychannel" }; + this.channels.set(channel.id, channel); + } + } + if (description.type == "pranswer") { + this.pendingLocalDescription = description; + this.signalingState = "have-local-pranswer"; + this.onsignalingstatechange(this.signalingState); + } + } + } else { + this.pendingLocalDescription = null; + this.pendingRemoteDescription = null; + this.signalingState = "stable"; + this.onsignalingstatechange(this.signalingState); + } + + if (this.videoTracks.size != 0 || this.audioTracks.size != 0) { + this._mockGatheringIceCandidate(this.videoTracks.size + this.audioTracks.size); + } + + //fire ontrack with new tracks, after using tracks clear. + if (this.ontrack) { + for (const track of this.videoTracks.values()) { + this.ontrack({ track: track }); + } + this.videoTracks.clear(); + + for (const track of this.audioTracks.values()) { + this.ontrack({ track: track }); + } + this.audioTracks.clear(); + } + + if (this.ondatachannel) { + for (const channel of this.channels.values()) { + this.ondatachannel({ channel: channel }); + } + this.channels.clear(); + } + } + + async _mockGatheringIceCandidate(count) { + this.iceGatheringState = "gathering"; + if (this.onicegatheringstatechange) { + this.onicegatheringstatechange(this.iceGatheringState); + } + for (let index = 0; index < count; index++) { + await this.delay(); + const newCandidate = { candidate: getUniqueId(), sdpMLineIndex: index, sdpMid: index }; + if (this.onicecandidate) { + this.onicecandidate(newCandidate); + } + } + this.iceGatheringState = "complete"; + if (this.onicegatheringstatechange) { + this.onicegatheringstatechange(this.iceGatheringState); + } + if (this.onicecandidate) { + this.onicecandidate({ candidate: null, sdpMLineIndex: null, sdpMid: null }); + } + } + + async addIceCandidate(candidate) { + await this.delay(); + if (this.remoteDescription == null) { + throw "InvalidStateError"; + } + this.candidates.push(candidate); + } + + _createTrackAndTransceiver(kind) { + const track = { id: getUniqueId(), kind: kind }; + if (kind == "video") { + this.videoTracks.set(track.id, track); + } else { + this.audioTracks.set(track.id, track); + } + const transceiver = { direction: "sendrecv", sender: { track: track }, receiver: null, setCodecPreferences: (codecs) => { console.log(codecs); } }; + this.transceivers.set(this.transceiverCount++, transceiver); + } +} + +export class SessionDescriptionMock { + + constructor(object) { + this.sdp = object.sdp; + this.type = object.type; + } + + sdp; + type; +} + +export class IceCandidateMock { + constructor(object) { + this.candidate = object.candidate; + this.sdpMLineIndex = object.sdpMLineIndex; + this.sdpMid = object.sdpMid; + } + + candidate; + sdpMLineIndex; + sdpMid; +} \ No newline at end of file diff --git a/client/test/pointercorrect.test.js b/client/test/pointercorrect.test.js new file mode 100644 index 0000000..d85857f --- /dev/null +++ b/client/test/pointercorrect.test.js @@ -0,0 +1,45 @@ +import { + LetterBoxType, + PointerCorrector +} from "../src/pointercorrect.js"; + +import {DOMRect} from "./domrect.js"; +import {DOMHTMLVideoElement} from "./domvideoelement.js"; + +describe(`PointerCorrector.map`, () => { + test('letterboxType', () => { + const rect = new DOMRect(10, 10, 200, 200); + const element = new DOMHTMLVideoElement(rect); + let corrector = new PointerCorrector(50, 100, element); + expect(corrector.letterBoxType).toBe(LetterBoxType.Vertical); + corrector.reset(100, 50, element); + expect(corrector.letterBoxType).toBe(LetterBoxType.Horizontal); + }); + test('letterboxSize', () => { + const rect = new DOMRect(0, 0, 100, 100); + const element = new DOMHTMLVideoElement(rect); + let corrector = new PointerCorrector(50, 100, element); + expect(corrector.letterBoxSize).toBe(25); + }); + test('contentRect', () => { + const rect = new DOMRect(0, 0, 100, 100); + const element = new DOMHTMLVideoElement(rect); + let corrector = new PointerCorrector(50, 100, element); + expect(corrector.contentRect.x).toBe(25); + expect(corrector.contentRect.y).toBe(0); + expect(corrector.contentRect.width).toBe(50); + expect(corrector.contentRect.height).toBe(100); + }); + test('mapping', () => { + const rect = new DOMRect(10, 10, 200, 200); + const element = new DOMHTMLVideoElement(rect); + const videoWidth = 100; + const videoHeight = 100; + let corrector = new PointerCorrector(videoWidth, videoHeight, element); + const position = [10, 10]; + const newPosition = corrector.map(position); + expect(newPosition[0]).toBe(0); + expect(newPosition[1]).toBe(100); + }); + +}); diff --git a/client/test/renderstreaming.test.js b/client/test/renderstreaming.test.js new file mode 100644 index 0000000..f2bc5f8 --- /dev/null +++ b/client/test/renderstreaming.test.js @@ -0,0 +1,187 @@ +import { MockSignaling, reset } from "./mocksignaling.js"; +import { waitFor, getUniqueId, getRTCConfiguration } from "./testutils.js"; +import { RenderStreaming } from "../src/renderstreaming.js"; + +describe.each([ + { mode: "private" }, + { mode: "public" } +])('renderstreaming test', ({ mode }) => { + const connectionId1 = "12345"; + + test(`createConnection in ${mode} mode`, async () => { + reset(mode == "private"); + const config = getRTCConfiguration(); + const renderstreaming = new RenderStreaming(new MockSignaling(), config); + await renderstreaming.start(); + + let isConnect = false; + renderstreaming.onConnect = () => isConnect = true; + await renderstreaming.createConnection(connectionId1); + await waitFor(() => isConnect); + expect(isConnect).toBe(true); + + await renderstreaming.stop(); + }); + + test(`addTrack in ${mode} mode`, async () => { + reset(mode == "private"); + const config = getRTCConfiguration(); + const renderstreaming = new RenderStreaming(new MockSignaling(), config); + await renderstreaming.start(); + + let isConnect = false; + renderstreaming.onConnect = () => isConnect = true; + await renderstreaming.createConnection(connectionId1); + await waitFor(() => isConnect); + expect(isConnect).toBe(true); + expect(renderstreaming.getTransceivers(connectionId1).length).toBe(0); + + const track = { id: getUniqueId(), kind: "audio" }; + renderstreaming.addTrack(track); + expect(renderstreaming.getTransceivers(connectionId1).length).toBe(1); + + let isDisconnect = false; + renderstreaming.onDisconnect = () => isDisconnect = true; + await renderstreaming.deleteConnection(); + await waitFor(() => isDisconnect); + expect(isDisconnect).toBe(true); + + await renderstreaming.stop(); + }); + + test(`createChannel in ${mode} mode`, async () => { + reset(mode == "private"); + const config = getRTCConfiguration(); + const renderstreaming = new RenderStreaming(new MockSignaling(), config); + await renderstreaming.start(); + + let isConnect = false; + renderstreaming.onConnect = () => isConnect = true; + await renderstreaming.createConnection(connectionId1); + await waitFor(() => isConnect); + expect(isConnect).toBe(true); + expect(renderstreaming.getTransceivers(connectionId1).length).toBe(0); + + const label = "testlabel"; + const channel = renderstreaming.createDataChannel(label); + expect(channel.label).toBe(label); + + let isDisconnect = false; + renderstreaming.onDisconnect = () => isDisconnect = true; + await renderstreaming.deleteConnection(); + await waitFor(() => isDisconnect); + expect(isDisconnect).toBe(true); + + await renderstreaming.stop(); + }); + + test(`onTrackEvent in ${mode} mode`, async () => { + reset(mode == "private"); + + const config = getRTCConfiguration(); + const renderstreaming1 = new RenderStreaming(new MockSignaling(), config); + const renderstreaming2 = new RenderStreaming(new MockSignaling(), config); + await renderstreaming1.start(); + await renderstreaming2.start(); + + let isConnect1 = false; + renderstreaming1.onConnect = () => isConnect1 = true; + let isConnect2 = false; + renderstreaming2.onConnect = () => isConnect2 = true; + + await renderstreaming1.createConnection(connectionId1); + await renderstreaming2.createConnection(connectionId1); + await waitFor(() => isConnect1 && isConnect2); + expect(isConnect1).toBe(true); + expect(isConnect2).toBe(true); + + let isGotOffer1 = false; + let isOnTrack1 = false; + let isGotAnswer2 = false; + renderstreaming1.onGotOffer = () => { isGotOffer1 = true; }; + renderstreaming1.onTrackEvent = () => { isOnTrack1 = true; }; + renderstreaming2.onGotAnswer = () => { isGotAnswer2 = true; }; + + expect(renderstreaming1.getTransceivers(connectionId1).length).toBe(0); + + const track = { id: getUniqueId(), kind: "audio" }; + renderstreaming2.addTrack(track); + expect(renderstreaming2.getTransceivers(connectionId1).length).toBe(1); + await waitFor(() => isGotOffer1); + expect(isGotOffer1).toBe(true); + + await waitFor(() => isOnTrack1); + expect(isOnTrack1).toBe(true); + expect(renderstreaming1.getTransceivers(connectionId1).length).toBe(1); + + await waitFor(() => isGotAnswer2); + expect(isGotAnswer2).toBe(true); + + let isDisconnect1 = false; + renderstreaming1.onDisconnect = () => isDisconnect1 = true; + let isDisconnect2 = false; + renderstreaming2.onDisconnect = () => isDisconnect2 = true; + + await renderstreaming1.deleteConnection(); + await renderstreaming2.deleteConnection(); + await waitFor(() => isDisconnect1 && isDisconnect2); + expect(isDisconnect1).toBe(true); + expect(isDisconnect2).toBe(true); + + await renderstreaming1.stop(); + await renderstreaming2.stop(); + }); + + test(`onAddDataChannel in ${mode} mode`, async () => { + reset(mode == "private"); + + const config = getRTCConfiguration(); + const renderstreaming1 = new RenderStreaming(new MockSignaling(), config); + const renderstreaming2 = new RenderStreaming(new MockSignaling(), config); + await renderstreaming1.start(); + await renderstreaming2.start(); + + let isConnect1 = false; + renderstreaming1.onConnect = () => isConnect1 = true; + let isConnect2 = false; + renderstreaming2.onConnect = () => isConnect2 = true; + + await renderstreaming1.createConnection(connectionId1); + await renderstreaming2.createConnection(connectionId1); + await waitFor(() => isConnect1 && isConnect2); + expect(isConnect1).toBe(true); + expect(isConnect2).toBe(true); + + let isGotOffer1 = false; + let isAddChannel1 = false; + let isGotAnswer2 = false; + renderstreaming1.onGotOffer = () => { isGotOffer1 = true; }; + renderstreaming1.onAddChannel = () => { isAddChannel1 = true; }; + renderstreaming2.onGotAnswer = () => { isGotAnswer2 = true; }; + + renderstreaming2.createDataChannel("testchannel"); + await waitFor(() => isGotOffer1); + expect(isGotOffer1).toBe(true); + + await waitFor(() => isAddChannel1); + expect(isAddChannel1).toBe(true); + + await waitFor(() => isGotAnswer2); + expect(isGotAnswer2).toBe(true); + + let isDisconnect1 = false; + renderstreaming1.onDisconnect = () => isDisconnect1 = true; + let isDisconnect2 = false; + renderstreaming2.onDisconnect = () => isDisconnect2 = true; + + await renderstreaming1.deleteConnection(); + await renderstreaming2.deleteConnection(); + await waitFor(() => isDisconnect1 && isDisconnect2); + expect(isDisconnect1).toBe(true); + expect(isDisconnect2).toBe(true); + + await renderstreaming1.stop(); + await renderstreaming2.stop(); + }); + +}); diff --git a/client/test/resizeobservermock.js b/client/test/resizeobservermock.js new file mode 100644 index 0000000..d7bc378 --- /dev/null +++ b/client/test/resizeobservermock.js @@ -0,0 +1,18 @@ +// mock class + +/* eslint-disable no-unused-vars */ +let instanceResize = null; +/* eslint-disable no-unused-vars */ +let callbackResize = null; + +export default class ResizeObserverMock { + constructor(callback) { + instanceResize = this; + callbackResize = callback; + } + disconnect() { } + /* eslint-disable no-unused-vars */ + observe(target, options) { } + /* eslint-disable no-unused-vars */ + unobserve(target) { } +} \ No newline at end of file diff --git a/client/test/sender.test.js b/client/test/sender.test.js new file mode 100644 index 0000000..d573702 --- /dev/null +++ b/client/test/sender.test.js @@ -0,0 +1,143 @@ +import { + InputRemoting, +} from "../src/inputremoting.js"; + +import { + Sender, + Observer +} from "../src/sender.js"; + +import {jest} from '@jest/globals'; +import {DOMRect} from "./domrect.js"; + +// mock + +class RTCDataChannel { + get readyState() { + return "open"; + } + /* eslint-disable no-unused-vars */ + send(message) { + } +} + +describe(`Sender`, () => { + let inputRemoting = null; + let sender = null; + let observer = null; + let events = {}; + let dc = null; + beforeEach(async () => { + // Empty our events before each test case + events = {}; + + // Define the addEventListener method with a Jest mock function + document.addEventListener = jest.fn((event, callback) => { + events[event] = callback; + }); + + document.removeEventListener = jest.fn((event, callback) => { + delete events[event]; + }); + document.getBoundingClientRect = function(){ return new DOMRect(0,0,0,0); }; + sender = new Sender(document); + inputRemoting = new InputRemoting(sender); + dc = new RTCDataChannel(); + observer = new Observer(dc); + }); + test('devices', () => { + sender.addMouse(); + expect(sender.devices.length).toBe(1); + sender.addKeyboard(); + expect(sender.devices.length).toBe(2); + }); + test('send messages while called startSending', () => { + jest.spyOn(dc, 'send'); + sender.addMouse(); + sender.addKeyboard(); + inputRemoting.subscribe(observer); + inputRemoting.startSending(); + expect(dc.send).toHaveBeenCalled(); + }); + describe('mouse', () => { + test('click', () => { + jest.spyOn(dc, 'send'); + sender.addMouse(); + inputRemoting.subscribe(observer); + inputRemoting.startSending(); + events.click( + new MouseEvent('click', { buttons:1, clientX:0, clientY:0} )); + expect(dc.send).toHaveBeenCalledWith(expect.any(ArrayBuffer)); + }); + test('mousemove', () => { + jest.spyOn(dc, 'send'); + sender.addMouse(); + inputRemoting.subscribe(observer); + inputRemoting.startSending(); + events.mousemove( + new MouseEvent('mousemove', { buttons:1, deltaX:0, deltaY:0 })); + expect(dc.send).toHaveBeenCalledWith(expect.any(ArrayBuffer)); + }); + test('wheel', () => { + jest.spyOn(dc, 'send'); + sender.addMouse(); + inputRemoting.subscribe(observer); + inputRemoting.startSending(); + events.wheel( + new WheelEvent('wheel', { wheelDelta:0, deltaX:0, deltaY:0 })); + expect(dc.send).toHaveBeenCalledWith(expect.any(ArrayBuffer)); + }); + }); + describe('keyboard', () => { + test('keydown', () => { + jest.spyOn(dc, 'send'); + sender.addKeyboard(); + inputRemoting.subscribe(observer); + inputRemoting.startSending(); + events.keydown( + new KeyboardEvent('keydown', { code: 'KeyA' })); + expect(dc.send).toHaveBeenCalledWith(expect.any(ArrayBuffer)); + }); + test('keydown repeat', () => { + jest.spyOn(dc, 'send'); + sender.addKeyboard(); + inputRemoting.subscribe(observer); + inputRemoting.startSending(); + events.keydown( + new KeyboardEvent('keydown', { code: 'KeyA', repeat: true })); + expect(dc.send).toHaveBeenCalledWith(expect.any(ArrayBuffer)); + }); + }); + describe('touchscreen', () => { + test('touchstart', () => { + jest.spyOn(dc, 'send'); + sender.addTouchscreen(); + inputRemoting.subscribe(observer); + inputRemoting.startSending(); + events.touchstart( + new TouchEvent("touchstart", { + changedTouches: [{ // InputInit + identifier: 0, + target: null, + clientX: 0, + clientY: 0, + screenX: 0, + screenY: 0, + pageX: 0, + pageY: 0, + radiusX: 0, + radiusY: 0, + rotationAngle: 0, + force: 0, + altitudeAngle: 0, + azimuthAngle:0, + touchType: "direct" + }] + })); + expect(dc.send).toHaveBeenCalledWith(expect.any(ArrayBuffer)); + }); + }); + describe('gamepad', () => { + //todo + }); +}); \ No newline at end of file diff --git a/client/test/signaling.test.js b/client/test/signaling.test.js new file mode 100644 index 0000000..5ed9e73 --- /dev/null +++ b/client/test/signaling.test.js @@ -0,0 +1,484 @@ +import { jest } from '@jest/globals'; +import * as Path from 'path'; +import { setup, teardown } from 'jest-dev-server'; +import { Signaling, WebSocketSignaling } from "../src/signaling.js"; +import { MockSignaling, reset } from "./mocksignaling.js"; +import { waitFor, sleep, serverExeName } from "./testutils.js"; + +const portNumber = 8081; +jest.setTimeout(10000); + +describe.each([ + { mode: "mock" }, + { mode: "http" }, + { mode: "websocket" }, +])('signaling test in public mode', ({ mode }) => { + let signaling1; + let signaling2; + const connectionId1 = "12345"; + const connectionId2 = "67890"; + const testsdp = "test sdp"; + const testcandidate = "test candidate"; + + beforeAll(async () => { + if (mode == "mock") { + reset(false); + signaling1 = new MockSignaling(1); + signaling2 = new MockSignaling(1); + } else { + const path = Path.resolve(`../bin~/${serverExeName()}`); + let cmd = `${path} -p ${portNumber}`; + if (mode == "http") { + cmd += " -t http"; + } + + await setup({ command: cmd, port: portNumber, usedPortAction: 'error' }); + + if (mode == "http") { + signaling1 = new Signaling(1); + signaling2 = new Signaling(1); + } + + if (mode == "websocket") { + signaling1 = new WebSocketSignaling(1); + signaling2 = new WebSocketSignaling(1); + } + } + + await signaling1.start(); + await signaling2.start(); + }); + + afterAll(async () => { + await signaling1.stop(); + await signaling2.stop(); + signaling1 = null; + signaling2 = null; + + if (mode == "mock") { + return; + } + + await teardown(); + // work around for linux, waitng kill server process + await sleep(1000); + }); + + test(`onConnect using ${mode}`, async () => { + const signaling1Spy = jest.spyOn(signaling1, 'dispatchEvent'); + let connectRes; + let disconnectRes; + signaling1.addEventListener('connect', (e) => connectRes = e.detail); + signaling1.addEventListener('disconnect', (e) => disconnectRes = e.detail); + + await signaling1.createConnection(connectionId1); + await waitFor(() => connectRes != null); + expect(connectRes.connectionId).toBe(connectionId1); + expect(connectRes.polite).toBe(true); + + await signaling1.deleteConnection(connectionId1); + await waitFor(() => disconnectRes != null); + expect(disconnectRes.connectionId).toBe(connectionId1); + + const disconnectCalledCount = signaling1Spy.mock.calls.map(x => x[0].type).filter(x => x == "disconnect").length; + expect(disconnectCalledCount).toBe(1); + + signaling1Spy.mockRestore(); + }); + + test(`onOffer using ${mode}`, async () => { + let connectRes1; + let disconnectRes1; + signaling1.addEventListener('connect', (e) => connectRes1 = e.detail); + signaling1.addEventListener('disconnect', (e) => disconnectRes1 = e.detail); + + let connectRes2; + let disconnectRes2; + let offerRes2; + signaling2.addEventListener('connect', (e) => connectRes2 = e.detail); + signaling2.addEventListener('disconnect', (e) => disconnectRes2 = e.detail); + signaling2.addEventListener('offer', (e) => offerRes2 = e.detail); + + await signaling1.createConnection(connectionId1); + await signaling2.createConnection(connectionId2); + await waitFor(() => connectRes1 != null && connectRes2 != null); + expect(connectRes1.connectionId).toBe(connectionId1); + expect(connectRes2.connectionId).toBe(connectionId2); + + await signaling1.sendOffer(connectionId1, testsdp); + await waitFor(() => offerRes2 != null); + expect(offerRes2.connectionId).toBe(connectionId1); + expect(offerRes2.polite).toBe(false); + + await signaling1.deleteConnection(connectionId1); + await waitFor(() => disconnectRes1 != null); + expect(disconnectRes1.connectionId).toBe(connectionId1); + await signaling2.deleteConnection(connectionId2); + await waitFor(() => disconnectRes2 != null); + expect(disconnectRes2.connectionId).toBe(connectionId2); + }); + + test(`onAnswer using ${mode}`, async () => { + let connectRes1; + let disconnectRes1; + let answerRes1; + signaling1.addEventListener('connect', (e) => connectRes1 = e.detail); + signaling1.addEventListener('disconnect', (e) => disconnectRes1 = e.detail); + signaling1.addEventListener('answer', (e) => answerRes1 = e.detail); + + let connectRes2; + let disconnectRes2; + let offerRes2; + signaling2.addEventListener('connect', (e) => connectRes2 = e.detail); + signaling2.addEventListener('disconnect', (e) => disconnectRes2 = e.detail); + signaling2.addEventListener('offer', (e) => offerRes2 = e.detail); + + await signaling1.createConnection(connectionId1); + await signaling2.createConnection(connectionId2); + await waitFor(() => connectRes1 != null && connectRes2 != null); + + await signaling1.sendOffer(connectionId1, testsdp); + await waitFor(() => offerRes2 != null); + expect(offerRes2.connectionId).toBe(connectionId1); + expect(offerRes2.sdp).toBe(testsdp); + + signaling2.sendAnswer(connectionId1, testsdp); + await waitFor(() => answerRes1 != null); + expect(answerRes1.connectionId).toBe(connectionId1); + expect(answerRes1.sdp).toBe(testsdp); + + await signaling1.deleteConnection(connectionId1); + await waitFor(() => disconnectRes1 != null); + await signaling2.deleteConnection(connectionId2); + await waitFor(() => disconnectRes2 != null); + }); + + test(`onCandidate using ${mode}`, async () => { + let connectRes1; + let disconnectRes1; + let answerRes1; + let candidateRes1; + signaling1.addEventListener('connect', (e) => connectRes1 = e.detail); + signaling1.addEventListener('disconnect', (e) => disconnectRes1 = e.detail); + signaling1.addEventListener('answer', (e) => answerRes1 = e.detail); + signaling1.addEventListener('candidate', (e) => candidateRes1 = e.detail); + + let connectRes2; + let disconnectRes2; + let offerRes2; + let candidateRes2; + signaling2.addEventListener('connect', (e) => connectRes2 = e.detail); + signaling2.addEventListener('disconnect', (e) => disconnectRes2 = e.detail); + signaling2.addEventListener('offer', (e) => offerRes2 = e.detail); + signaling2.addEventListener('candidate', (e) => candidateRes2 = e.detail); + + await signaling1.createConnection(connectionId1); + await signaling2.createConnection(connectionId2); + await waitFor(() => connectRes1 != null && connectRes2 != null); + + await signaling1.sendOffer(connectionId1, testsdp); + await waitFor(() => offerRes2 != null); + expect(offerRes2.connectionId).toBe(connectionId1); + expect(offerRes2.sdp).toBe(testsdp); + + signaling2.sendAnswer(connectionId1, testsdp); + await waitFor(() => answerRes1 != null); + expect(answerRes1.connectionId).toBe(connectionId1); + expect(answerRes1.sdp).toBe(testsdp); + + await signaling2.sendCandidate(connectionId1, testcandidate, 1, 1); + await waitFor(() => candidateRes1 != null); + expect(candidateRes1.connectionId).toBe(connectionId1); + expect(candidateRes1.candidate).toBe(testcandidate); + expect(candidateRes1.sdpMid).toBe(1); + expect(candidateRes1.sdpMLineIndex).toBe(1); + + await signaling1.sendCandidate(connectionId1, testcandidate, 1, 1); + await waitFor(() => candidateRes2 != null); + expect(candidateRes2.connectionId).toBe(connectionId1); + expect(candidateRes2.candidate).toBe(testcandidate); + expect(candidateRes2.sdpMid).toBe(1); + expect(candidateRes2.sdpMLineIndex).toBe(1); + + await signaling1.deleteConnection(connectionId1); + await waitFor(() => disconnectRes1 != null); + await signaling2.deleteConnection(connectionId2); + await waitFor(() => disconnectRes2 != null); + }); +}); + +describe.each([ + { mode: "mock" }, + { mode: "http" }, + { mode: "websocket" }, +])('signaling test in private mode', ({ mode }) => { + let signaling1; + let signaling2; + const connectionId = "12345"; + const testsdp = "test sdp"; + const testcandidate = "test candidate"; + + beforeAll(async () => { + if (mode == "mock") { + reset(true); + signaling1 = new MockSignaling(1); + signaling2 = new MockSignaling(1); + return; + } + + const path = Path.resolve(`../bin~/${serverExeName()}`); + let cmd = `${path} -p ${portNumber} -m private`; + if (mode == "http") { + cmd += " -t http"; + } + + await setup({ command: cmd, port: portNumber, usedPortAction: 'error' }); + + if (mode == "http") { + signaling1 = new Signaling(1); + signaling2 = new Signaling(1); + } + + if (mode == "websocket") { + signaling1 = new WebSocketSignaling(1); + signaling2 = new WebSocketSignaling(1); + } + + await signaling1.start(); + await signaling2.start(); + }); + + afterAll(async () => { + await signaling1.stop(); + await signaling2.stop(); + signaling1 = null; + signaling2 = null; + + if (mode == "mock") { + return; + } + + await teardown(); + // work around for linux, waitng kill server process + await sleep(1000); + }); + + test(`onConnect using ${mode}`, async () => { + let connectRes1; + let disconnectRes1; + signaling1.addEventListener('connect', (e) => connectRes1 = e.detail); + signaling1.addEventListener('disconnect', (e) => disconnectRes1 = e.detail); + + let connectRes2; + let disconnectRes2; + signaling2.addEventListener('connect', (e) => connectRes2 = e.detail); + signaling2.addEventListener('disconnect', (e) => disconnectRes2 = e.detail); + + await signaling1.createConnection(connectionId); + await waitFor(() => connectRes1 != null); + expect(connectRes1.connectionId).toBe(connectionId); + expect(connectRes1.polite).toBe(false); + + await signaling2.createConnection(connectionId); + await waitFor(() => connectRes2 != null); + expect(connectRes2.connectionId).toBe(connectionId); + expect(connectRes2.polite).toBe(true); + + await sleep(signaling1.interval * 2); + + await signaling1.deleteConnection(connectionId); + await waitFor(() => disconnectRes1 != null && disconnectRes2 != null); + expect(disconnectRes1.connectionId).toBe(connectionId); + expect(disconnectRes2.connectionId).toBe(connectionId); + + disconnectRes2 = null; + await signaling2.deleteConnection(connectionId); + await waitFor(() => disconnectRes2 != null); + }); + + test(`onOffer using ${mode}`, async () => { + let connectRes1; + let disconnectRes1; + signaling1.addEventListener('connect', (e) => connectRes1 = e.detail); + signaling1.addEventListener('disconnect', (e) => disconnectRes1 = e.detail); + + let connectRes2; + let disconnectRes2; + let offerRes2; + signaling2.addEventListener('connect', (e) => connectRes2 = e.detail); + signaling2.addEventListener('disconnect', (e) => disconnectRes2 = e.detail); + signaling2.addEventListener('offer', (e) => offerRes2 = e.detail); + + await signaling1.createConnection(connectionId); + await waitFor(() => connectRes1 != null); + expect(connectRes1.connectionId).toBe(connectionId); + + signaling1.sendOffer(connectionId, testsdp); + await sleep(signaling1.interval * 2); + // Do not receive offer other signaling if not connected same sendoffer connectionId in private mode + expect(offerRes2).toBeUndefined(); + + await signaling2.createConnection(connectionId); + await waitFor(() => connectRes2 != null); + expect(connectRes2.connectionId).toBe(connectionId); + + await signaling1.sendOffer(connectionId, testsdp); + await waitFor(() => offerRes2 != null); + expect(offerRes2.connectionId).toBe(connectionId); + expect(offerRes2.polite).toBe(true); + + await signaling1.deleteConnection(connectionId); + await waitFor(() => disconnectRes1 != null && disconnectRes2 != null); + expect(disconnectRes1.connectionId).toBe(connectionId); + expect(disconnectRes2.connectionId).toBe(connectionId); + + disconnectRes2 = null; + await signaling2.deleteConnection(connectionId); + await waitFor(() => disconnectRes2 != null); + }); + + test(`onAnswer using ${mode}`, async () => { + let connectRes1; + let disconnectRes1; + let answerRes1; + signaling1.addEventListener('connect', (e) => connectRes1 = e.detail); + signaling1.addEventListener('disconnect', (e) => disconnectRes1 = e.detail); + signaling1.addEventListener('answer', (e) => answerRes1 = e.detail); + + let connectRes2; + let disconnectRes2; + let offerRes2; + signaling2.addEventListener('connect', (e) => connectRes2 = e.detail); + signaling2.addEventListener('disconnect', (e) => disconnectRes2 = e.detail); + signaling2.addEventListener('offer', (e) => offerRes2 = e.detail); + + await signaling1.createConnection(connectionId); + await signaling2.createConnection(connectionId); + await waitFor(() => connectRes1 != null && connectRes2 != null); + + await signaling1.sendOffer(connectionId, testsdp); + await waitFor(() => offerRes2 != null); + expect(offerRes2.connectionId).toBe(connectionId); + expect(offerRes2.sdp).toBe(testsdp); + + await signaling2.sendAnswer(connectionId, testsdp); + await waitFor(() => answerRes1 != null); + expect(answerRes1.connectionId).toBe(connectionId); + expect(answerRes1.sdp).toBe(testsdp); + + await signaling1.deleteConnection(connectionId); + await waitFor(() => disconnectRes1 != null && disconnectRes2 != null); + expect(disconnectRes1.connectionId).toBe(connectionId); + expect(disconnectRes2.connectionId).toBe(connectionId); + + disconnectRes2 = null; + await signaling2.deleteConnection(connectionId); + await waitFor(() => disconnectRes2 != null); + }); + + test(`onCandidate using ${mode}`, async () => { + let connectRes1; + let disconnectRes1; + let answerRes1; + let candidateRes1; + signaling1.addEventListener('connect', (e) => connectRes1 = e.detail); + signaling1.addEventListener('disconnect', (e) => disconnectRes1 = e.detail); + signaling1.addEventListener('answer', (e) => answerRes1 = e.detail); + signaling1.addEventListener('candidate', (e) => candidateRes1 = e.detail); + + let connectRes2; + let disconnectRes2; + let offerRes2; + let candidateRes2; + signaling2.addEventListener('connect', (e) => connectRes2 = e.detail); + signaling2.addEventListener('disconnect', (e) => disconnectRes2 = e.detail); + signaling2.addEventListener('offer', (e) => offerRes2 = e.detail); + signaling2.addEventListener('candidate', (e) => candidateRes2 = e.detail); + + await signaling1.createConnection(connectionId); + await signaling2.createConnection(connectionId); + await waitFor(() => connectRes1 != null && connectRes2 != null); + + await signaling1.sendOffer(connectionId, testsdp); + await waitFor(() => offerRes2 != null); + expect(offerRes2.connectionId).toBe(connectionId); + expect(offerRes2.sdp).toBe(testsdp); + + await signaling2.sendAnswer(connectionId, testsdp); + await waitFor(() => answerRes1 != null); + expect(answerRes1.connectionId).toBe(connectionId); + expect(answerRes1.sdp).toBe(testsdp); + + await signaling2.sendCandidate(connectionId, testcandidate, 1, 1); + await waitFor(() => candidateRes1 != null); + expect(candidateRes1.connectionId).toBe(connectionId); + expect(candidateRes1.candidate).toBe(testcandidate); + expect(candidateRes1.sdpMLineIndex).toBe(1); + expect(candidateRes1.sdpMid).toBe(1); + + await signaling1.sendCandidate(connectionId, testcandidate, 1, 1); + await waitFor(() => candidateRes2 != null); + expect(candidateRes2.connectionId).toBe(connectionId); + expect(candidateRes2.candidate).toBe(testcandidate); + expect(candidateRes2.sdpMLineIndex).toBe(1); + expect(candidateRes2.sdpMid).toBe(1); + + await signaling1.deleteConnection(connectionId); + await waitFor(() => disconnectRes1 != null && disconnectRes2 != null); + expect(disconnectRes1.connectionId).toBe(connectionId); + expect(disconnectRes2.connectionId).toBe(connectionId); + + disconnectRes2 = null; + await signaling2.deleteConnection(connectionId); + await waitFor(() => disconnectRes2 != null); + }); + + test(`notReceiveOwnOfferAnswer using ${mode}`, async () => { + let connectRes1; + let disconnectRes1; + let offerRes1; + let answerRes1; + signaling1.addEventListener('connect', (e) => connectRes1 = e.detail); + signaling1.addEventListener('disconnect', (e) => disconnectRes1 = e.detail); + + let connectRes2; + let disconnectRes2; + let offerRes2; + let answerRes2; + signaling2.addEventListener('connect', (e) => connectRes2 = e.detail); + signaling2.addEventListener('disconnect', (e) => disconnectRes2 = e.detail); + + await signaling1.createConnection(connectionId); + await signaling2.createConnection(connectionId); + await waitFor(() => connectRes1 != null && connectRes2 != null); + + signaling1.addEventListener('offer', (e) => offerRes1 = e.detail); + signaling2.addEventListener('offer', (e) => offerRes2 = e.detail); + await signaling1.sendOffer(connectionId, testsdp); + await waitFor(() => offerRes2 != null); + await sleep(signaling1.interval * 2); + expect(offerRes1).toBeUndefined(); + expect(offerRes2).not.toBeUndefined(); + expect(offerRes2.connectionId).toBe(connectionId); + expect(offerRes2.sdp).toBe(testsdp); + + signaling1.addEventListener('answer', (e) => answerRes1 = e.detail); + signaling2.addEventListener('answer', (e) => answerRes2 = e.detail); + await signaling2.sendAnswer(connectionId, testsdp); + await waitFor(() => answerRes1 != null); + await sleep(signaling2.interval * 2); + expect(answerRes1).not.toBeUndefined(); + expect(answerRes1.connectionId).toBe(connectionId); + expect(answerRes1.sdp).toBe(testsdp); + expect(answerRes2).toBeUndefined(); + + await signaling1.deleteConnection(connectionId); + await waitFor(() => disconnectRes1 != null && disconnectRes2 != null); + expect(disconnectRes1.connectionId).toBe(connectionId); + expect(disconnectRes2.connectionId).toBe(connectionId); + + disconnectRes2 = null; + await signaling2.deleteConnection(connectionId); + await waitFor(() => disconnectRes2 != null); + }); +}); diff --git a/client/test/testutils.js b/client/test/testutils.js new file mode 100644 index 0000000..80f60db --- /dev/null +++ b/client/test/testutils.js @@ -0,0 +1,39 @@ +import process from "process"; + +export function waitFor(conditionFunction) { + + const poll = resolve => { + if (conditionFunction()) resolve(); + else setTimeout(() => poll(resolve), 100); + }; + + return new Promise(poll); +} + +export async function sleep(milisecond) { + return new Promise(resolve => setTimeout(resolve, milisecond)); +} + +export function serverExeName() { + switch (process.platform) { + case 'win32': + return 'webserver.exe'; + case 'darwin': + return 'webserver_mac'; + case 'linux': + return 'webserver'; + default: + return null; + } +} + +export function getUniqueId() { + return new Date().getTime().toString(16) + Math.floor(1000 * Math.random()).toString(16); +} + +export function getRTCConfiguration() { + let config = {}; + config.sdpSemantics = 'unified-plan'; + config.iceServers = [{ urls: ['stun:stun.l.google.com:19302'] }]; + return config; +} \ No newline at end of file diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..886ea2b --- /dev/null +++ b/jest.config.js @@ -0,0 +1,195 @@ +/* + * For a detailed explanation regarding each configuration property, visit: + * https://jestjs.io/docs/configuration + */ + +module.exports = { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "/private/var/folders/wt/swsbjj0x061bdb0y4dqc0g4c0000gn/T/jest_dx", + + // Automatically clear mock calls and instances between every test + // clearMocks: false, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: undefined, + + // The directory where Jest should output its coverage files + coverageDirectory: "coverage", + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "/node_modules/" + // ], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: "v8", + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + moduleFileExtensions: [ + "js", + "jsx", + "ts", + "tsx", + "json", + "node" + ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + // moduleNameMapper: {}, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + // preset: undefined, + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state between every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state between every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + // roots: [ + // "" + // ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + testEnvironment: "node", + + // Options that will be passed to the testEnvironment + testEnvironmentOptions: { + url: "http://localhost" + }, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + testMatch: [ + "**/__tests__/**/*.[jt]s?(x)", + "**/?(*.)+(spec|test).[tj]s?(x)" + ], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "/node_modules/" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", + + // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" + // timers: "real", + + // A map from regular expressions to paths to transformers + transform: { + '^.+\\.tsx?$': 'ts-jest' + }, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "/node_modules/", + // "\\.pnp\\.[^\\/]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4679f12 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,15324 @@ +{ + "name": "webserver", + "version": "3.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "webserver", + "version": "3.1.0", + "dependencies": { + "@types/express": "^4.17.13", + "@types/node": "^18.7.15", + "@types/ws": "^8.5.3", + "cors": "^2.8.5", + "debug": "~4.3.4", + "express": "~4.18.1", + "morgan": "^1.10.0", + "multer": "^2.1.1", + "swagger-jsdoc": "^6.2.1", + "swagger-ui-express": "^4.5.0", + "uuid": "^9.0.0", + "ws": "^8.8.1" + }, + "bin": { + "webserver": "build/index.js" + }, + "devDependencies": { + "@jest-mock/express": "^2.0.1", + "@types/jest": "^29.0.2", + "@types/morgan": "^1.9.3", + "@types/swagger-jsdoc": "^6.0.1", + "@types/swagger-ui-express": "^4.1.3", + "@typescript-eslint/eslint-plugin": "^5.36.2", + "@typescript-eslint/parser": "^5.36.2", + "eslint": "^8.23.0", + "eslint-plugin-jest": "^27.0.1", + "jest": "^29.0.2", + "jest-websocket-mock": "^2.4.0", + "mock-socket": "^9.1.5", + "newman": "^6.0.0", + "pkg": "^5.8.0", + "ts-jest": "^29.0.2", + "ts-node": "^10.9.1", + "typescript": "^4.8.2" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz", + "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.2.tgz", + "integrity": "sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.18.2", + "@babel/helper-compilation-targets": "^7.18.2", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helpers": "^7.18.2", + "@babel/parser": "^7.18.0", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.2", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", + "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.2", + "@jridgewell/gen-mapping": "^0.3.0", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", + "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz", + "integrity": "sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.10", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name/node_modules/@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables/node_modules/@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz", + "integrity": "sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.0", + "@babel/types": "^7.18.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz", + "integrity": "sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration/node_modules/@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.2.tgz", + "integrity": "sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz", + "integrity": "sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", + "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template/node_modules/@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template/node_modules/@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz", + "integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.1.tgz", + "integrity": "sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@faker-js/faker": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-5.5.3.tgz", + "integrity": "sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==", + "dev": true + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", + "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest-mock/express": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@jest-mock/express/-/express-2.0.1.tgz", + "integrity": "sha512-DvW4WMGoNBEOGx1XRCe8yZyQ7ju5dWmBN57H0zPio7AqQo27QawI+o30f9xVdLnS0bAF2wbmQQfS61LxL/t8bQ==", + "dev": true, + "dependencies": { + "@types/express": "^4.17.13" + } + }, + "node_modules/@jest/console": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.0.2.tgz", + "integrity": "sha512-Fv02ijyhF4D/Wb3DvZO3iBJQz5DnzpJEIDBDbvje8Em099N889tNMUnBw7SalmSuOI+NflNG40RA1iK71kImPw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.0.2", + "jest-util": "^29.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.0.2.tgz", + "integrity": "sha512-imP5M6cdpHEOkmcuFYZuM5cTG1DAF7ZlVNCq1+F7kbqme2Jcl+Kh4M78hihM76DJHNkurbv4UVOnejGxBKEmww==", + "dev": true, + "dependencies": { + "@jest/console": "^29.0.2", + "@jest/reporters": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.0.0", + "jest-config": "^29.0.2", + "jest-haste-map": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-resolve": "^29.0.2", + "jest-resolve-dependencies": "^29.0.2", + "jest-runner": "^29.0.2", + "jest-runtime": "^29.0.2", + "jest-snapshot": "^29.0.2", + "jest-util": "^29.0.2", + "jest-validate": "^29.0.2", + "jest-watcher": "^29.0.2", + "micromatch": "^4.0.4", + "pretty-format": "^29.0.2", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/@jest/transform": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.0.2.tgz", + "integrity": "sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/@jest/core/node_modules/jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@jest/environment": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.0.2.tgz", + "integrity": "sha512-Yf+EYaLOrVCgts/aTS5nGznU4prZUPa5k9S63Yct8YSOKj2jkdS17hHSUKhk5jxDFMyCy1PXknypDw7vfgc/mA==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "jest-mock": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.0.2.tgz", + "integrity": "sha512-y/3geZ92p2/zovBm/F+ZjXUJ3thvT9IRzD6igqaWskFE2aR0idD+N/p5Lj/ZautEox/9RwEc6nqergebeh72uQ==", + "dev": true, + "dependencies": { + "expect": "^29.0.2", + "jest-snapshot": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.0.2.tgz", + "integrity": "sha512-+wcQF9khXKvAEi8VwROnCWWmHfsJYCZAs5dmuMlJBKk57S6ZN2/FQMIlo01F29fJyT8kV/xblE7g3vkIdTLOjw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.0.2.tgz", + "integrity": "sha512-2JhQeWU28fvmM5r33lxg6BxxkTKaVXs6KMaJ6eXSM8ml/MaWkt2BvbIO8G9KWAJFMdBXWbn+2h9OK1/s5urKZA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^29.0.2", + "jest-mock": "^29.0.2", + "jest-util": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.0.2.tgz", + "integrity": "sha512-4hcooSNJCVXuTu07/VJwCWW6HTnjLtQdqlcGisK6JST7z2ixa8emw4SkYsOk7j36WRc2ZUEydlUePnOIOTCNXg==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.0.2", + "@jest/expect": "^29.0.2", + "@jest/types": "^29.0.2", + "jest-mock": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.0.2.tgz", + "integrity": "sha512-Kr41qejRQHHkCgWHC9YwSe7D5xivqP4XML+PvgwsnRFaykKdNflDUb4+xLXySOU+O/bPkVdFpGzUpVNSJChCrw==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.0.2", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/@jest/transform": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.0.2.tgz", + "integrity": "sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/@jest/reporters/node_modules/jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.0.0.tgz", + "integrity": "sha512-nOr+0EM8GiHf34mq2GcJyz/gYFyLQ2INDhAylrZJ9mMWoW21mLBfZa0BUVPPMxVYrLjeiRe2Z7kWXOGnS0TFhQ==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.0.2.tgz", + "integrity": "sha512-b5rDc0lLL6Kx73LyCx6370k9uZ8o5UKdCpMS6Za3ke7H9y8PtAU305y6TeghpBmf2In8p/qqi3GpftgzijSsNw==", + "dev": true, + "dependencies": { + "@jest/console": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.0.2.tgz", + "integrity": "sha512-fsyZqHBlXNMv5ZqjQwCuYa2pskXCO0DVxh5aaVCuAtwzHuYEGrhordyEncBLQNuCGQSYgElrEEmS+7wwFnnMKw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.0.2", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer/node_modules/jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/@jest/test-sequencer/node_modules/jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer/node_modules/jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@jest/types": { + "version": "29.6.1", + "resolved": "https://registry.npmmirror.com/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/node_modules/@jest/schemas": { + "version": "29.6.0", + "resolved": "https://registry.npmmirror.com/@jest/schemas/-/schemas-29.6.0.tgz", + "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", + "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@postman/form-data": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz", + "integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@postman/tough-cookie": { + "version": "4.1.3-postman.1", + "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.3-postman.1.tgz", + "integrity": "sha512-txpgUqZOnWYnUHZpHjkfb0IwVH4qJmyq77pPnJLlfhMtdCLMFTEeQHlzQiK906aaNCe4NEB5fGJHo9uzGbFMeA==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@postman/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@postman/tunnel-agent": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.3.tgz", + "integrity": "sha512-k57fzmAZ2PJGxfOA4SGR05ejorHbVAa/84Hxh/2nAztjNXc4ZjOm9NUIk6/Z6LCrBvJZqjRZbN8e/nROVUPVdg==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true + }, + "node_modules/@sinclair/typebox": { + "version": "0.24.35", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.35.tgz", + "integrity": "sha512-iN6ehuDndiTiDz2F+Orv/+oHJR+PrGv+38oghCddpsW4YEZl5qyLsWxSwYUWrKEOfjpGtXDFW6scJtjpzSLeSw==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.1.19", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", + "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.17.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.17.1.tgz", + "integrity": "sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", + "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.3", + "resolved": "https://registry.npmmirror.com/@types/jest/-/jest-29.5.3.tgz", + "integrity": "sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, + "node_modules/@types/morgan": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.3.tgz", + "integrity": "sha512-BiLcfVqGBZCyNCnCH3F4o2GmDLrpy0HeBVnNlyZG4fo88ZiE9SoiBe3C+2ezuwbjlEyT+PDZ17//TAlRxAn75Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "18.7.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.15.tgz", + "integrity": "sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ==" + }, + "node_modules/@types/prettier": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", + "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "node_modules/@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/swagger-jsdoc": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", + "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", + "dev": true + }, + "node_modules/@types/swagger-ui-express": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", + "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz", + "integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.36.2.tgz", + "integrity": "sha512-OwwR8LRwSnI98tdc2z7mJYgY60gf7I9ZfGjN5EjCwwns9bdTuQfAXcsjSB2wSQ/TVNYSGKf4kzVXbNGaZvwiXw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.36.2", + "@typescript-eslint/type-utils": "5.36.2", + "@typescript-eslint/utils": "5.36.2", + "debug": "^4.3.4", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.36.2.tgz", + "integrity": "sha512-qS/Kb0yzy8sR0idFspI9Z6+t7mqk/oRjnAYfewG+VN73opAUvmYL3oPIMmgOX6CnQS6gmVIXGshlb5RY/R22pA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.36.2", + "@typescript-eslint/types": "5.36.2", + "@typescript-eslint/typescript-estree": "5.36.2", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.36.2.tgz", + "integrity": "sha512-cNNP51L8SkIFSfce8B1NSUBTJTu2Ts4nWeWbFrdaqjmn9yKrAaJUBHkyTZc0cL06OFHpb+JZq5AUHROS398Orw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.36.2", + "@typescript-eslint/visitor-keys": "5.36.2" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.36.2.tgz", + "integrity": "sha512-rPQtS5rfijUWLouhy6UmyNquKDPhQjKsaKH0WnY6hl/07lasj8gPaH2UD8xWkePn6SC+jW2i9c2DZVDnL+Dokw==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.36.2", + "@typescript-eslint/utils": "5.36.2", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.36.2.tgz", + "integrity": "sha512-9OJSvvwuF1L5eS2EQgFUbECb99F0mwq501w0H0EkYULkhFa19Qq7WFbycdw1PexAc929asupbZcgjVIe6OK/XQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.36.2.tgz", + "integrity": "sha512-8fyH+RfbKc0mTspfuEjlfqA4YywcwQK2Amcf6TDOwaRLg7Vwdu4bZzyvBZp4bjt1RRjQ5MDnOZahxMrt2l5v9w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.36.2", + "@typescript-eslint/visitor-keys": "5.36.2", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.36.2.tgz", + "integrity": "sha512-uNcopWonEITX96v9pefk9DC1bWMdkweeSsewJ6GeC7L6j2t0SJywisgkr9wUTtXk90fi2Eljj90HSHm3OGdGRg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.36.2", + "@typescript-eslint/types": "5.36.2", + "@typescript-eslint/typescript-estree": "5.36.2", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.36.2.tgz", + "integrity": "sha512-BtRvSR6dEdrNt7Net2/XDjbYKU5Ml6GqJgVfXT0CxTCJlnIqK7rAGreuWKMT2t8cFUT2Msv5oxw0GMRD7T5J7A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.36.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/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==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", + "dev": true + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "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" + } + ] + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "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.10.3", + "raw-body": "2.5.1", + "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==", + "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==" + }, + "node_modules/body-parser/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==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "dev": true, + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/browserslist": { + "version": "4.20.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.4.tgz", + "integrity": "sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001349", + "electron-to-chromium": "^1.4.147", + "escalade": "^3.1.1", + "node-releases": "^2.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/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==" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + }, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001349", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001349.tgz", + "integrity": "sha512-VFaWW3jeo6DLU5rwdiasosxhYSduJgSGil4cSyX3/85fbctlE58pXAkWyuRmVA0r2RxsOSVYUTZcySJ8WpbTxw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chardet": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-1.6.0.tgz", + "integrity": "sha512-+QOTw3otC4+FxdjK9RopGpNOglADbr4WPFi0SonkO99JbpkTPbMxmdm4NenhF5Zs+4gPXLI1+y2uazws5TMe8w==", + "dev": true + }, + "node_modules/charset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz", + "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/ci-info": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.1.tgz", + "integrity": "sha512-SXgeMX9VwDe7iFFaEWkA5AstuER9YKqy4EhHqr4DVqkwmD9rpVimkMKWHdjn30Ja45txyjhSn63lVX69eVCckg==", + "dev": true + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "node_modules/cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "dev": true, + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "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, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", + "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/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==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "node_modules/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==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "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": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csv-parse": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", + "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==", + "dev": true + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "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, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.0.0.tgz", + "integrity": "sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.147", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.147.tgz", + "integrity": "sha512-czclPqxLMPqPMkahKcske4TaS5lcznsc26ByBlEFDU8grTBVK9C5W6K9I6oEEhm4Ai4jTihGnys90xY1yjXcRg==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "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==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "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==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.23.0.tgz", + "integrity": "sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.3.1", + "@humanwhocodes/config-array": "^0.10.4", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@humanwhocodes/module-importer": "^1.0.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.0.1.tgz", + "integrity": "sha512-LosUsrkwVSs/8Z/I8Hqn5vWgTEsHrfIquDEKOsV8/cl+gbFR4tiRCE1AimEotsHjSC0Rx1tYm6vPhw8C3ktmmg==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^5.10.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", + "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/expect": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.0.2.tgz", + "integrity": "sha512-JeJlAiLKn4aApT4pzUXBVxl3NaZidWIOdg//smaIlP9ZMBDkHZGFd9ubphUZP9pUyDEo7bC6M0IIZR51o75qQw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.0.2", + "jest-get-type": "^29.0.0", + "jest-matcher-utils": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-util": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "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" + } + }, + "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==", + "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==" + }, + "node_modules/express/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==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/filesize": { + "version": "10.0.12", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.0.12.tgz", + "integrity": "sha512-6RS9gDchbn+qWmtV2uSjo5vmKizgfCQeb5jKmqx8HyzA3MoLqqyQxN+QcjkGBJt7FjJ9qFce67Auyya5rRRbpw==", + "dev": true, + "engines": { + "node": ">= 10.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "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==", + "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==" + }, + "node_modules/finalhandler/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==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", + "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", + "dev": true + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "node_modules/gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", + "dev": true, + "dependencies": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dev": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/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==", + "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-reasons": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/http-reasons/-/http-reasons-0.1.0.tgz", + "integrity": "sha512-P6kYh0lKZ+y29T2Gqz+RlC9WBLhKe8kDmcJ+A+611jFfxdPsbMRQ5aNmFRM3lENqFkK+HTTL+tlQviAiv0AbLQ==", + "dev": true + }, + "node_modules/http-signature": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.14.1" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/httpntlm": { + "version": "1.8.13", + "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.8.13.tgz", + "integrity": "sha512-2F2FDPiWT4rewPzNMg3uPhNkP3NExENlUGADRUDPQvuftuUTGW98nLZtGemCIW3G40VhWZYgkIDcQFAwZ3mf2Q==", + "dev": true, + "funding": [ + { + "type": "paypal", + "url": "https://www.paypal.com/donate/?hosted_button_id=2CKNJLZJBW8ZC" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/samdecrock" + } + ], + "dependencies": { + "des.js": "^1.0.1", + "httpreq": ">=0.4.22", + "js-md4": "^0.3.2", + "underscore": "~1.12.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/httpreq": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-1.1.1.tgz", + "integrity": "sha512-uhSZLPPD2VXXOSN8Cni3kIsoFHaU2pT/nySEU/fHr/ePbqHYr0jeiQRmUKLEirC09SFPsdMoA7LU7UXMd/w0Kw==", + "dev": true, + "engines": { + "node": ">= 6.15.1" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/into-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", + "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", + "dev": true, + "dependencies": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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==", + "engines": { + "node": ">= 0.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 + }, + "node_modules/is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", + "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.0.2.tgz", + "integrity": "sha512-enziNbNUmXTcTaTP/Uq5rV91r0Yqy2UKzLUIabxMpGm9YHz8qpbJhiRnNVNvm6vzWfzt/0o97NEHH8/3udoClA==", + "dev": true, + "dependencies": { + "@jest/core": "^29.0.2", + "@jest/types": "^29.0.2", + "import-local": "^3.0.2", + "jest-cli": "^29.0.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.0.0.tgz", + "integrity": "sha512-28/iDMDrUpGoCitTURuDqUzWQoWmOmOKOFST1mi2lwh62X4BFf6khgH3uSuo1e49X/UDjuApAj3w0wLOex4VPQ==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-circus": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.0.2.tgz", + "integrity": "sha512-YTPEsoE1P1X0bcyDQi3QIkpt2Wl9om9k2DQRuLFdS5x8VvAKSdYAVJufgvudhnKgM8WHvvAzhBE+1DRQB8x1CQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.0.2", + "@jest/expect": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.0.2", + "jest-matcher-utils": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-runtime": "^29.0.2", + "jest-snapshot": "^29.0.2", + "jest-util": "^29.0.2", + "p-limit": "^3.1.0", + "pretty-format": "^29.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-cli": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.0.2.tgz", + "integrity": "sha512-tlf8b+4KcUbBGr25cywIi3+rbZ4+G+SiG8SvY552m9sRZbXPafdmQRyeVE/C/R8K+TiBAMrTIUmV2SlStRJ40g==", + "dev": true, + "dependencies": { + "@jest/core": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/types": "^29.0.2", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.0.2", + "jest-util": "^29.0.2", + "jest-validate": "^29.0.2", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.0.2.tgz", + "integrity": "sha512-RU4gzeUNZAFktYVzDGimDxeYoaiTnH100jkYYZgldqFamaZukF0IqmFx8+QrzVeEWccYg10EEJT3ox1Dq5b74w==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.0.2", + "@jest/types": "^29.0.2", + "babel-jest": "^29.0.2", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.0.2", + "jest-environment-node": "^29.0.2", + "jest-get-type": "^29.0.0", + "jest-regex-util": "^29.0.0", + "jest-resolve": "^29.0.2", + "jest-runner": "^29.0.2", + "jest-util": "^29.0.2", + "jest-validate": "^29.0.2", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.0.2", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/@jest/transform": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.0.2.tgz", + "integrity": "sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-config/node_modules/babel-jest": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.0.2.tgz", + "integrity": "sha512-yTu4/WSi/HzarjQtrJSwV+/0maoNt+iP0DmpvFJdv9yY+5BuNle8TbheHzzcSWj5gIHfuhpbLYHWRDYhWKyeKQ==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.0.2", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.0.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/jest-config/node_modules/babel-plugin-jest-hoist": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.0.2.tgz", + "integrity": "sha512-eBr2ynAEFjcebVvu8Ktx580BD1QKCrBG1XwEUTXJe285p9HA/4hOhfWCFRQhTKSyBV0VzjhG7H91Eifz9s29hg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-config/node_modules/babel-preset-jest": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.0.2.tgz", + "integrity": "sha512-BeVXp7rH5TK96ofyEnHjznjLMQ2nAeDJ+QzxKnHAAMs0RgrQsCywjAN8m4mOm5Di0pxU//3AoEeJJrerMH5UeA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.0.2", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/jest-config/node_modules/jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-config/node_modules/jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-config/node_modules/jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-config/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest-diff": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.0.2.tgz", + "integrity": "sha512-b9l9970sa1rMXH1owp2Woprmy42qIwwll/htsw4Gf7+WuSp5bZxNhkKHDuCGKL+HoHn1KhcC+tNEeAPYBkD2Jg==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.0.0", + "jest-get-type": "^29.0.0", + "pretty-format": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.0.0.tgz", + "integrity": "sha512-s5Kpra/kLzbqu9dEjov30kj1n4tfu3e7Pl8v+f8jOkeWNqM6Ds8jRaJfZow3ducoQUrf2Z4rs2N5S3zXnb83gw==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.0.2.tgz", + "integrity": "sha512-+sA9YjrJl35iCg0W0VCrgCVj+wGhDrrKQ+YAqJ/DHBC4gcDFAeePtRRhpJnX9gvOZ63G7gt52pwp2PesuSEx0Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "chalk": "^4.0.0", + "jest-get-type": "^29.0.0", + "jest-util": "^29.0.2", + "pretty-format": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.0.2.tgz", + "integrity": "sha512-4Fv8GXVCToRlMzDO94gvA8iOzKxQ7rhAbs8L+j8GPyTxGuUiYkV+63LecGeVdVhsL2KXih1sKnoqmH6tp89J7Q==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.0.2", + "@jest/fake-timers": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "jest-mock": "^29.0.2", + "jest-util": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.0.0.tgz", + "integrity": "sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.0.2.tgz", + "integrity": "sha512-5f0493qDeAxjUldkBSQg5D1cLadRgZVyWpTQvfJeQwQUpHQInE21AyVHVv64M7P2Ue8Z5EZ4BAcoDS/dSPPgMw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.0.0", + "pretty-format": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.0.2.tgz", + "integrity": "sha512-s62YkHFBfAx0JLA2QX1BlnCRFwHRobwAv2KP1+YhjzF6ZCbCVrf1sG8UJyn62ZUsDaQKpoo86XMTjkUyO5aWmQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.0.2", + "jest-get-type": "^29.0.0", + "pretty-format": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.2.tgz", + "integrity": "sha512-kcJAgms3ckJV0wUoLsAM40xAhY+pb9FVSZwicjFU9PFkaTNmqh9xd99/CzKse48wPM1ANUQKmp03/DpkY+lGrA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.0.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.0.2.tgz", + "integrity": "sha512-giWXOIT23UCxHCN2VUfUJ0Q7SmiqQwfSFXlCaIhW5anITpNQ+3vuLPQdKt5wkuwM37GrbFyHIClce8AAK9ft9g==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "@types/node": "*" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-resolve": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.0.2.tgz", + "integrity": "sha512-V3uLjSA+EHxLtjIDKTBXnY71hyx+8lusCqPXvqzkFO1uCGvVpjBfuOyp+KOLBNSuY61kM2jhepiMwt4eiJS+Vw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.0.2", + "jest-validate": "^29.0.2", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.0.2.tgz", + "integrity": "sha512-fSAu6eIG7wtGdnPJUkVVdILGzYAP9Dj/4+zvC8BrGe8msaUMJ9JeygU0Hf9+Uor6/icbuuzQn5See1uajLnAqg==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.0.0", + "jest-snapshot": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies/node_modules/jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/node_modules/jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-resolve/node_modules/jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/node_modules/jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest-runner": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.0.2.tgz", + "integrity": "sha512-+D82iPZejI8t+SfduOO1deahC/QgLFf8aJBO++Znz3l2ETtOMdM7K4ATsGWzCFnTGio5yHaRifg1Su5Ybza5Nw==", + "dev": true, + "dependencies": { + "@jest/console": "^29.0.2", + "@jest/environment": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.0.0", + "jest-environment-node": "^29.0.2", + "jest-haste-map": "^29.0.2", + "jest-leak-detector": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-resolve": "^29.0.2", + "jest-runtime": "^29.0.2", + "jest-util": "^29.0.2", + "jest-watcher": "^29.0.2", + "jest-worker": "^29.0.2", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/@jest/transform": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.0.2.tgz", + "integrity": "sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-runner/node_modules/jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-runner/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest-runtime": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.0.2.tgz", + "integrity": "sha512-DO6F81LX4okOgjJLkLySv10E5YcV5NHUbY1ZqAUtofxdQE+q4hjH0P2gNsY8x3z3sqgw7O/+919SU4r18Fcuig==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.0.2", + "@jest/fake-timers": "^29.0.2", + "@jest/globals": "^29.0.2", + "@jest/source-map": "^29.0.0", + "@jest/test-result": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-mock": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-resolve": "^29.0.2", + "jest-snapshot": "^29.0.2", + "jest-util": "^29.0.2", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/@jest/transform": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.0.2.tgz", + "integrity": "sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-runtime/node_modules/jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest-snapshot": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.0.2.tgz", + "integrity": "sha512-26C4PzGKaX5gkoKg8UzYGVy2HPVcTaROSkf0gwnHu3lGeTB7bAIJBovvVPZoiJ20IximJELQs/r8WSDRCuGX2A==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.0.2", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.0.2", + "jest-get-type": "^29.0.0", + "jest-haste-map": "^29.0.2", + "jest-matcher-utils": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-util": "^29.0.2", + "natural-compare": "^1.4.0", + "pretty-format": "^29.0.2", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@jest/transform": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.0.2.tgz", + "integrity": "sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-snapshot/node_modules/jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest-util": { + "version": "29.6.2", + "resolved": "https://registry.npmmirror.com/jest-util/-/jest-util-29.6.2.tgz", + "integrity": "sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.0.2.tgz", + "integrity": "sha512-AeRKm7cEucSy7tr54r3LhiGIXYvOILUwBM1S7jQkKs6YelwAlWKsmZGVrQR7uwsd31rBTnR5NQkODi1Z+6TKIQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.0.2", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.0.0", + "leven": "^3.1.0", + "pretty-format": "^29.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.0.2.tgz", + "integrity": "sha512-ds2bV0oyUdYoyrUTv4Ga5uptz4cEvmmP/JzqDyzZZanvrIn8ipxg5l3SDOAIiyuAx1VdHd2FBzeXPFO5KPH8vQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^29.0.2", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-websocket-mock": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.4.0.tgz", + "integrity": "sha512-AOwyuRw6fgROXHxMOiTDl1/T4dh3fV4jDquha5N0csS/PNp742HeTZWPAuKppVRSQ8s3fUGgJHoyZT9JDO0hMA==", + "dev": true, + "dependencies": { + "jest-diff": "^28.0.2", + "mock-socket": "^9.1.0" + } + }, + "node_modules/jest-websocket-mock/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-websocket-mock/node_modules/diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-websocket-mock/node_modules/jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-websocket-mock/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-websocket-mock/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jose": { + "version": "4.14.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", + "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-md4": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==", + "dev": true + }, + "node_modules/js-sha512": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz", + "integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "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, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/liquid-json": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/liquid-json/-/liquid-json-0.3.1.tgz", + "integrity": "sha512-wUayTU8MS827Dam6MxgD72Ui+KOSF+u/eIqpatOtjnvgJ0+mnDq33uC2M7J0tPK+upe/DpUAuK4JUU89iBoNKQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead." + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead." + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-format": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mime-format/-/mime-format-2.0.1.tgz", + "integrity": "sha512-XxU3ngPbEnrYnNbIX+lYSaYg0M01v6p2ntd2YaFksTu0vayaw5OJvbdRyWs07EYRlLED5qadUZ+xo+XhOvFhwg==", + "dev": true, + "dependencies": { + "charset": "^1.0.0" + } + }, + "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==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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 + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/mock-socket": { + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.1.5.tgz", + "integrity": "sha512-3DeNIcsQixWHHKk6NdoBhWI4t1VMj5/HzfnI1rE/pLl5qKx7+gd4DNA07ehTaZ6MoUU053si6Hd+YtiM/tQZfg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multer": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz", + "integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "type-is": "^1.6.18" + }, + "engines": { + "node": ">= 10.16.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/multistream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/multistream/-/multistream-4.1.0.tgz", + "integrity": "sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "once": "^1.4.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/multistream/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "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==", + "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 + }, + "node_modules/newman": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/newman/-/newman-6.0.0.tgz", + "integrity": "sha512-QaANQC5b6ga348MezIVRI9ZmMs+cg3MdYIp0tSEauH2tmWOAR9+cghNsFJNjU9JPui3jJp1ALC7pQq6g3Jqpxw==", + "dev": true, + "dependencies": { + "@postman/tough-cookie": "4.1.3-postman.1", + "async": "3.2.4", + "chardet": "1.6.0", + "cli-progress": "3.12.0", + "cli-table3": "0.6.3", + "colors": "1.4.0", + "commander": "11.0.0", + "csv-parse": "4.16.3", + "filesize": "10.0.12", + "liquid-json": "0.3.1", + "lodash": "4.17.21", + "mkdirp": "3.0.1", + "postman-collection": "4.2.1", + "postman-collection-transformer": "4.1.7", + "postman-request": "2.88.1-postman.33", + "postman-runtime": "7.33.0", + "pretty-ms": "7.0.1", + "semver": "7.5.4", + "serialised-error": "1.1.3", + "word-wrap": "1.2.5", + "xmlbuilder": "15.1.1" + }, + "bin": { + "newman": "bin/newman.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/node-abi": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", + "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", + "dev": true, + "dependencies": { + "semver": "^5.4.1" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "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-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-oauth1": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-oauth1/-/node-oauth1-1.3.0.tgz", + "integrity": "sha512-0yggixNfrA1KcBwvh/Hy2xAS1Wfs9dcg6TdFf2zN7gilcAigMdrtZ4ybrBSXBgLvGDw9V1p2MRnGBMq7XjTWLg==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", + "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", + "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "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==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "peer": true + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/pkg/-/pkg-5.8.0.tgz", + "integrity": "sha512-8h9PUDYFi+LOMLbIyGRdP21g08mAtHidSpofSrf8LWhxUWGHymaRzcopEGiynB5EhQmZUKM6PQ9kCImV2TpdjQ==", + "dev": true, + "dependencies": { + "@babel/generator": "7.18.2", + "@babel/parser": "7.18.4", + "@babel/types": "7.18.4", + "chalk": "^4.1.2", + "fs-extra": "^9.1.0", + "globby": "^11.1.0", + "into-stream": "^6.0.0", + "is-core-module": "2.9.0", + "minimist": "^1.2.6", + "multistream": "^4.1.0", + "pkg-fetch": "3.4.2", + "prebuild-install": "6.1.4", + "resolve": "^1.22.0", + "stream-meter": "^1.0.4" + }, + "bin": { + "pkg": "lib-es5/bin.js" + }, + "peerDependencies": { + "node-notifier": ">=9.0.1" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-fetch": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.4.2.tgz", + "integrity": "sha512-0+uijmzYcnhC0hStDjm/cl2VYdrmVVBpe7Q8k9YBojxmR5tG8mvR9/nooQq3QSXiQqORDVOTY3XqMEqJVIzkHA==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "fs-extra": "^9.1.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.6", + "progress": "^2.0.3", + "semver": "^7.3.5", + "tar-fs": "^2.1.1", + "yargs": "^16.2.0" + }, + "bin": { + "pkg-fetch": "lib-es5/bin.js" + } + }, + "node_modules/pkg-fetch/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "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/pkg-fetch/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, + "engines": { + "node": ">=10" + } + }, + "node_modules/postman-collection": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.2.1.tgz", + "integrity": "sha512-DFLt3/yu8+ldtOTIzmBUctoupKJBOVK4NZO0t68K2lIir9smQg7OdQTBjOXYy+PDh7u0pSDvD66tm93eBHEPHA==", + "dev": true, + "dependencies": { + "@faker-js/faker": "5.5.3", + "file-type": "3.9.0", + "http-reasons": "0.1.0", + "iconv-lite": "0.6.3", + "liquid-json": "0.3.1", + "lodash": "4.17.21", + "mime-format": "2.0.1", + "mime-types": "2.1.35", + "postman-url-encoder": "3.0.5", + "semver": "7.5.4", + "uuid": "8.3.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postman-collection-transformer": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/postman-collection-transformer/-/postman-collection-transformer-4.1.7.tgz", + "integrity": "sha512-SxJkm/LnlFZs2splUBnS4jQFicgBptghpm4voHtNnaum3Ad64E2MHLV4fJhv58dVUmFwdSwdQUN3m2q0iLecnQ==", + "dev": true, + "dependencies": { + "commander": "8.3.0", + "inherits": "2.0.4", + "lodash": "4.17.21", + "semver": "7.5.4", + "strip-json-comments": "3.1.1" + }, + "bin": { + "postman-collection-transformer": "bin/transform-collection.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postman-collection-transformer/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/postman-collection/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postman-collection/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, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/postman-request": { + "version": "2.88.1-postman.33", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.33.tgz", + "integrity": "sha512-uL9sCML4gPH6Z4hreDWbeinKU0p0Ke261nU7OvII95NU22HN6Dk7T/SaVPaj6T4TsQqGKIFw6/woLZnH7ugFNA==", + "dev": true, + "dependencies": { + "@postman/form-data": "~3.1.1", + "@postman/tough-cookie": "~4.1.3-postman.1", + "@postman/tunnel-agent": "^0.6.3", + "aws-sign2": "~0.7.0", + "aws4": "^1.12.0", + "brotli": "^1.3.3", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "har-validator": "~5.1.3", + "http-signature": "~1.3.1", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "^2.1.35", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.3", + "safe-buffer": "^5.1.2", + "stream-length": "^1.0.2", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postman-request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/postman-request/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, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/postman-runtime": { + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/postman-runtime/-/postman-runtime-7.33.0.tgz", + "integrity": "sha512-cYCb+5Y12FwZU/T3gOj2SKiOz38pisVLc0tdppb+ZlG7iqn5aLgxghJwhjG62pZCV6uixKiQX1hNdLSk9a9Xtw==", + "dev": true, + "dependencies": { + "@postman/tough-cookie": "4.1.3-postman.1", + "async": "3.2.4", + "aws4": "1.12.0", + "handlebars": "4.7.8", + "httpntlm": "1.8.13", + "jose": "4.14.4", + "js-sha512": "0.8.0", + "lodash": "4.17.21", + "mime-types": "2.1.35", + "node-oauth1": "1.3.0", + "performance-now": "2.1.0", + "postman-collection": "4.2.0", + "postman-request": "2.88.1-postman.33", + "postman-sandbox": "4.2.7", + "postman-url-encoder": "3.0.5", + "serialised-error": "1.1.3", + "strip-json-comments": "3.1.1", + "uuid": "8.3.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/postman-runtime/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postman-runtime/node_modules/postman-collection": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.2.0.tgz", + "integrity": "sha512-tvOLgN1h6Kab6dt43PmBoV5kYO/YUta3x0C2QqfmbzmHZe47VTpZ/+gIkGlbNhjKNPUUub5X6ehxYKoaTYdy1w==", + "dev": true, + "dependencies": { + "@faker-js/faker": "5.5.3", + "file-type": "3.9.0", + "http-reasons": "0.1.0", + "iconv-lite": "0.6.3", + "liquid-json": "0.3.1", + "lodash": "4.17.21", + "mime-format": "2.0.1", + "mime-types": "2.1.35", + "postman-url-encoder": "3.0.5", + "semver": "7.5.4", + "uuid": "8.3.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postman-runtime/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, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/postman-sandbox": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/postman-sandbox/-/postman-sandbox-4.2.7.tgz", + "integrity": "sha512-/EcCrKnb/o+9iLS4u+H76E0kBomJFjPptVjoDiq1uZ7Es/4aTv0MAX+0aoDxdDO+0h9sl8vy65uKQwyjN7AOaw==", + "dev": true, + "dependencies": { + "lodash": "4.17.21", + "postman-collection": "4.2.0", + "teleport-javascript": "1.0.0", + "uvm": "2.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postman-sandbox/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postman-sandbox/node_modules/postman-collection": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.2.0.tgz", + "integrity": "sha512-tvOLgN1h6Kab6dt43PmBoV5kYO/YUta3x0C2QqfmbzmHZe47VTpZ/+gIkGlbNhjKNPUUub5X6ehxYKoaTYdy1w==", + "dev": true, + "dependencies": { + "@faker-js/faker": "5.5.3", + "file-type": "3.9.0", + "http-reasons": "0.1.0", + "iconv-lite": "0.6.3", + "liquid-json": "0.3.1", + "lodash": "4.17.21", + "mime-format": "2.0.1", + "mime-types": "2.1.35", + "postman-url-encoder": "3.0.5", + "semver": "7.5.4", + "uuid": "8.3.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postman-sandbox/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, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/postman-url-encoder": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-3.0.5.tgz", + "integrity": "sha512-jOrdVvzUXBC7C+9gkIkpDJ3HIxOHTIqjpQ4C1EMt1ZGeMvSEpbFCKq23DEfgsj46vMnDgyQf+1ZLp2Wm+bKSsA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", + "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", + "dev": true, + "dependencies": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.21.0", + "npmlog": "^4.0.1", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.2.tgz", + "integrity": "sha512-wp3CdtUa3cSJVFn3Miu5a1+pxc1iPIQTenOAn+x5erXeN1+ryTcLesV5pbK/rlW5EKwp27x38MoYfNGaNXDDhg==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "dev": true, + "dependencies": { + "parse-ms": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "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" + } + ] + }, + "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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "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/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/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, + "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 + }, + "node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", + "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/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==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "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==", + "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==" + }, + "node_modules/send/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==" + }, + "node_modules/send/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==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialised-error": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/serialised-error/-/serialised-error-1.1.3.tgz", + "integrity": "sha512-vybp3GItaR1ZtO2nxZZo8eOo7fnVaNtP3XE2vJKgzkKR2bagCkdJ1EpYYhEMd3qu/80DwQk9KjsNSxE3fXWq0g==", + "dev": true, + "dependencies": { + "object-hash": "^1.1.2", + "stack-trace": "0.0.9", + "uuid": "^3.0.0" + } + }, + "node_modules/serialised-error/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "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, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "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" + } + ] + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dev": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", + "integrity": "sha512-vjUc6sfgtgY0dxCdnc40mK6Oftjo9+2K8H/NG81TMhgL392FtiPA9tn9RLyTxXmTLPJPjF3VyzFp6bsWFLisMQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz", + "integrity": "sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg==", + "dev": true, + "dependencies": { + "bluebird": "^2.6.2" + } + }, + "node_modules/stream-meter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stream-meter/-/stream-meter-1.0.4.tgz", + "integrity": "sha1-Uq+Vql6nYKJJFxZwTb/5D3Ov3R0=", + "dev": true, + "dependencies": { + "readable-stream": "^2.1.4" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "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==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "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, + "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, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "dependencies": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/swagger-jsdoc/node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/swagger-jsdoc/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.32.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.0.tgz", + "integrity": "sha512-nKZB0OuDvacB0s/lC2gbge+RigYvGRGpLLMWMFxaTUwfM+CfndVk9Th2IaTinqXiz6Mn26GK2zriCpv6/+5m3Q==", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.3.tgz", + "integrity": "sha512-CDje4PndhTD2HkgyKH3pab+LKspDeB/NhPN2OF1j+piYIamQqBYwAXWESOT1Yju2xFg51bRW9sUng2WxDjzArw==", + "dependencies": { + "swagger-ui-dist": ">=4.11.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/teleport-javascript": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/teleport-javascript/-/teleport-javascript-1.0.0.tgz", + "integrity": "sha512-j1llvWVFyEn/6XIFDfX5LAU43DXe0GCt3NfXDwJ8XpRRMkS+i50SAkonAONBy+vxwPFBd50MFU8a2uj8R/ccLg==", + "dev": true + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.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 + }, + "node_modules/ts-jest": { + "version": "29.1.1", + "resolved": "https://registry.npmmirror.com/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, + "node_modules/typescript": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz", + "integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/underscore": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", + "dev": true + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "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, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/uvm": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/uvm/-/uvm-2.1.1.tgz", + "integrity": "sha512-BZ5w8adTpNNr+zczOBRpaX/hH8UPKAf7fmCnidrcsqt3bn8KT9bDIfuS7hgRU9RXgiN01su2pwysBONY6w8W5w==", + "dev": true, + "dependencies": { + "flatted": "3.2.6" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", + "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/validator": { + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "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, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "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": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/write-file-atomic": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.1.tgz", + "integrity": "sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "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/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "engines": { + "node": ">=8.0" + } + }, + "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, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "requires": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==" + }, + "@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==" + }, + "@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "requires": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + } + }, + "@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dev": true, + "requires": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "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, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "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 + }, + "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 + }, + "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, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/compat-data": { + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz", + "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==", + "dev": true + }, + "@babel/core": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.2.tgz", + "integrity": "sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.18.2", + "@babel/helper-compilation-targets": "^7.18.2", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helpers": "^7.18.2", + "@babel/parser": "^7.18.0", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.2", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", + "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", + "dev": true, + "requires": { + "@babel/types": "^7.18.2", + "@jridgewell/gen-mapping": "^0.3.0", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", + "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz", + "integrity": "sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.10", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + }, + "dependencies": { + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-module-transforms": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz", + "integrity": "sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.0", + "@babel/types": "^7.18.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", + "dev": true + }, + "@babel/helper-simple-access": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz", + "integrity": "sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ==", + "dev": true, + "requires": { + "@babel/types": "^7.18.2" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + }, + "dependencies": { + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "dev": true + }, + "@babel/helpers": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.2.tgz", + "integrity": "sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg==", + "dev": true, + "requires": { + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.2" + } + }, + "@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "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, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "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 + }, + "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 + }, + "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, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz", + "integrity": "sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==", + "dev": true + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", + "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "dependencies": { + "@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "dev": true + }, + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/traverse": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "dev": true, + "requires": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "dev": true + }, + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz", + "integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true + }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, + "@eslint/eslintrc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.1.tgz", + "integrity": "sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@faker-js/faker": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-5.5.3.tgz", + "integrity": "sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==", + "dev": true + }, + "@humanwhocodes/config-array": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", + "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest-mock/express": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@jest-mock/express/-/express-2.0.1.tgz", + "integrity": "sha512-DvW4WMGoNBEOGx1XRCe8yZyQ7ju5dWmBN57H0zPio7AqQo27QawI+o30f9xVdLnS0bAF2wbmQQfS61LxL/t8bQ==", + "dev": true, + "requires": { + "@types/express": "^4.17.13" + } + }, + "@jest/console": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.0.2.tgz", + "integrity": "sha512-Fv02ijyhF4D/Wb3DvZO3iBJQz5DnzpJEIDBDbvje8Em099N889tNMUnBw7SalmSuOI+NflNG40RA1iK71kImPw==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.0.2", + "jest-util": "^29.0.2", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.0.2.tgz", + "integrity": "sha512-imP5M6cdpHEOkmcuFYZuM5cTG1DAF7ZlVNCq1+F7kbqme2Jcl+Kh4M78hihM76DJHNkurbv4UVOnejGxBKEmww==", + "dev": true, + "requires": { + "@jest/console": "^29.0.2", + "@jest/reporters": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.0.0", + "jest-config": "^29.0.2", + "jest-haste-map": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-resolve": "^29.0.2", + "jest-resolve-dependencies": "^29.0.2", + "jest-runner": "^29.0.2", + "jest-runtime": "^29.0.2", + "jest-snapshot": "^29.0.2", + "jest-util": "^29.0.2", + "jest-validate": "^29.0.2", + "jest-watcher": "^29.0.2", + "micromatch": "^4.0.4", + "pretty-format": "^29.0.2", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "@jest/transform": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.0.2.tgz", + "integrity": "sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + } + }, + "jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true + }, + "jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/environment": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.0.2.tgz", + "integrity": "sha512-Yf+EYaLOrVCgts/aTS5nGznU4prZUPa5k9S63Yct8YSOKj2jkdS17hHSUKhk5jxDFMyCy1PXknypDw7vfgc/mA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "jest-mock": "^29.0.2" + } + }, + "@jest/expect": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.0.2.tgz", + "integrity": "sha512-y/3geZ92p2/zovBm/F+ZjXUJ3thvT9IRzD6igqaWskFE2aR0idD+N/p5Lj/ZautEox/9RwEc6nqergebeh72uQ==", + "dev": true, + "requires": { + "expect": "^29.0.2", + "jest-snapshot": "^29.0.2" + } + }, + "@jest/expect-utils": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.0.2.tgz", + "integrity": "sha512-+wcQF9khXKvAEi8VwROnCWWmHfsJYCZAs5dmuMlJBKk57S6ZN2/FQMIlo01F29fJyT8kV/xblE7g3vkIdTLOjw==", + "dev": true, + "requires": { + "jest-get-type": "^29.0.0" + } + }, + "@jest/fake-timers": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.0.2.tgz", + "integrity": "sha512-2JhQeWU28fvmM5r33lxg6BxxkTKaVXs6KMaJ6eXSM8ml/MaWkt2BvbIO8G9KWAJFMdBXWbn+2h9OK1/s5urKZA==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^29.0.2", + "jest-mock": "^29.0.2", + "jest-util": "^29.0.2" + } + }, + "@jest/globals": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.0.2.tgz", + "integrity": "sha512-4hcooSNJCVXuTu07/VJwCWW6HTnjLtQdqlcGisK6JST7z2ixa8emw4SkYsOk7j36WRc2ZUEydlUePnOIOTCNXg==", + "dev": true, + "requires": { + "@jest/environment": "^29.0.2", + "@jest/expect": "^29.0.2", + "@jest/types": "^29.0.2", + "jest-mock": "^29.0.2" + } + }, + "@jest/reporters": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.0.2.tgz", + "integrity": "sha512-Kr41qejRQHHkCgWHC9YwSe7D5xivqP4XML+PvgwsnRFaykKdNflDUb4+xLXySOU+O/bPkVdFpGzUpVNSJChCrw==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.0.2", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "dependencies": { + "@jest/transform": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.0.2.tgz", + "integrity": "sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + } + }, + "jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true + }, + "jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@jest/source-map": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.0.0.tgz", + "integrity": "sha512-nOr+0EM8GiHf34mq2GcJyz/gYFyLQ2INDhAylrZJ9mMWoW21mLBfZa0BUVPPMxVYrLjeiRe2Z7kWXOGnS0TFhQ==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.0.2.tgz", + "integrity": "sha512-b5rDc0lLL6Kx73LyCx6370k9uZ8o5UKdCpMS6Za3ke7H9y8PtAU305y6TeghpBmf2In8p/qqi3GpftgzijSsNw==", + "dev": true, + "requires": { + "@jest/console": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.0.2.tgz", + "integrity": "sha512-fsyZqHBlXNMv5ZqjQwCuYa2pskXCO0DVxh5aaVCuAtwzHuYEGrhordyEncBLQNuCGQSYgElrEEmS+7wwFnnMKw==", + "dev": true, + "requires": { + "@jest/test-result": "^29.0.2", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "slash": "^3.0.0" + }, + "dependencies": { + "jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true + }, + "jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/types": { + "version": "29.6.1", + "resolved": "https://registry.npmmirror.com/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "dependencies": { + "@jest/schemas": { + "version": "29.6.0", + "resolved": "https://registry.npmmirror.com/@jest/schemas/-/schemas-29.6.0.tgz", + "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + } + } + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", + "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, + "@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, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@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 + }, + "@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, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@postman/form-data": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz", + "integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "@postman/tough-cookie": { + "version": "4.1.3-postman.1", + "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.3-postman.1.tgz", + "integrity": "sha512-txpgUqZOnWYnUHZpHjkfb0IwVH4qJmyq77pPnJLlfhMtdCLMFTEeQHlzQiK906aaNCe4NEB5fGJHo9uzGbFMeA==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "dependencies": { + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true + } + } + }, + "@postman/tunnel-agent": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.3.tgz", + "integrity": "sha512-k57fzmAZ2PJGxfOA4SGR05ejorHbVAa/84Hxh/2nAztjNXc4ZjOm9NUIk6/Z6LCrBvJZqjRZbN8e/nROVUPVdg==", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==" + }, + "@sinclair/typebox": { + "version": "0.24.35", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.35.tgz", + "integrity": "sha512-iN6ehuDndiTiDz2F+Orv/+oHJR+PrGv+38oghCddpsW4YEZl5qyLsWxSwYUWrKEOfjpGtXDFW6scJtjpzSLeSw==", + "dev": true + }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "@types/babel__core": { + "version": "7.1.19", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", + "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.17.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.17.1.tgz", + "integrity": "sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/body-parser": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", + "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "29.5.3", + "resolved": "https://registry.npmmirror.com/@types/jest/-/jest-29.5.3.tgz", + "integrity": "sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, + "@types/morgan": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.3.tgz", + "integrity": "sha512-BiLcfVqGBZCyNCnCH3F4o2GmDLrpy0HeBVnNlyZG4fo88ZiE9SoiBe3C+2ezuwbjlEyT+PDZ17//TAlRxAn75Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "18.7.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.15.tgz", + "integrity": "sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ==" + }, + "@types/prettier": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", + "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", + "dev": true + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/swagger-jsdoc": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", + "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", + "dev": true + }, + "@types/swagger-ui-express": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", + "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, + "@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "requires": { + "@types/node": "*" + } + }, + "@types/yargs": { + "version": "17.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz", + "integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.36.2.tgz", + "integrity": "sha512-OwwR8LRwSnI98tdc2z7mJYgY60gf7I9ZfGjN5EjCwwns9bdTuQfAXcsjSB2wSQ/TVNYSGKf4kzVXbNGaZvwiXw==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.36.2", + "@typescript-eslint/type-utils": "5.36.2", + "@typescript-eslint/utils": "5.36.2", + "debug": "^4.3.4", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/parser": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.36.2.tgz", + "integrity": "sha512-qS/Kb0yzy8sR0idFspI9Z6+t7mqk/oRjnAYfewG+VN73opAUvmYL3oPIMmgOX6CnQS6gmVIXGshlb5RY/R22pA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.36.2", + "@typescript-eslint/types": "5.36.2", + "@typescript-eslint/typescript-estree": "5.36.2", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.36.2.tgz", + "integrity": "sha512-cNNP51L8SkIFSfce8B1NSUBTJTu2Ts4nWeWbFrdaqjmn9yKrAaJUBHkyTZc0cL06OFHpb+JZq5AUHROS398Orw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.36.2", + "@typescript-eslint/visitor-keys": "5.36.2" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.36.2.tgz", + "integrity": "sha512-rPQtS5rfijUWLouhy6UmyNquKDPhQjKsaKH0WnY6hl/07lasj8gPaH2UD8xWkePn6SC+jW2i9c2DZVDnL+Dokw==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.36.2", + "@typescript-eslint/utils": "5.36.2", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.36.2.tgz", + "integrity": "sha512-9OJSvvwuF1L5eS2EQgFUbECb99F0mwq501w0H0EkYULkhFa19Qq7WFbycdw1PexAc929asupbZcgjVIe6OK/XQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.36.2.tgz", + "integrity": "sha512-8fyH+RfbKc0mTspfuEjlfqA4YywcwQK2Amcf6TDOwaRLg7Vwdu4bZzyvBZp4bjt1RRjQ5MDnOZahxMrt2l5v9w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.36.2", + "@typescript-eslint/visitor-keys": "5.36.2", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.36.2.tgz", + "integrity": "sha512-uNcopWonEITX96v9pefk9DC1bWMdkweeSsewJ6GeC7L6j2t0SJywisgkr9wUTtXk90fi2Eljj90HSHm3OGdGRg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.36.2", + "@typescript-eslint/types": "5.36.2", + "@typescript-eslint/typescript-estree": "5.36.2", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.36.2.tgz", + "integrity": "sha512-BtRvSR6dEdrNt7Net2/XDjbYKU5Ml6GqJgVfXT0CxTCJlnIqK7rAGreuWKMT2t8cFUT2Msv5oxw0GMRD7T5J7A==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.36.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "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==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "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 + }, + "asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true + }, + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "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 + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true + }, + "aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", + "dev": true + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "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 + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ==", + "dev": true + }, + "body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "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.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "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==", + "requires": { + "ee-first": "1.1.1" + } + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "dev": true, + "requires": { + "base64-js": "^1.1.2" + } + }, + "browserslist": { + "version": "4.20.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.4.tgz", + "integrity": "sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001349", + "electron-to-chromium": "^1.4.147", + "escalade": "^3.1.1", + "node-releases": "^2.0.5", + "picocolors": "^1.0.0" + } + }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "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==" + }, + "busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "requires": { + "streamsearch": "^1.1.0" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001349", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001349.tgz", + "integrity": "sha512-VFaWW3jeo6DLU5rwdiasosxhYSduJgSGil4cSyX3/85fbctlE58pXAkWyuRmVA0r2RxsOSVYUTZcySJ8WpbTxw==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "chardet": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-1.6.0.tgz", + "integrity": "sha512-+QOTw3otC4+FxdjK9RopGpNOglADbr4WPFi0SonkO99JbpkTPbMxmdm4NenhF5Zs+4gPXLI1+y2uazws5TMe8w==", + "dev": true + }, + "charset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz", + "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==", + "dev": true + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "ci-info": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.1.tgz", + "integrity": "sha512-SXgeMX9VwDe7iFFaEWkA5AstuER9YKqy4EhHqr4DVqkwmD9rpVimkMKWHdjn30Ja45txyjhSn63lVX69eVCckg==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "dev": true, + "requires": { + "string-width": "^4.2.3" + } + }, + "cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dev": true, + "requires": { + "@colors/colors": "1.5.0", + "string-width": "^4.2.0" + } + }, + "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, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", + "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "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==", + "requires": { + "safe-buffer": "5.2.1" + }, + "dependencies": { + "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==" + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "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 + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "csv-parse": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", + "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "requires": { + "mimic-response": "^2.0.0" + } + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "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 + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "diff-sequences": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.0.0.tgz", + "integrity": "sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA==", + "dev": true + }, + "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, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "requires": { + "esutils": "^2.0.2" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "electron-to-chromium": { + "version": "1.4.147", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.147.tgz", + "integrity": "sha512-czclPqxLMPqPMkahKcske4TaS5lcznsc26ByBlEFDU8grTBVK9C5W6K9I6oEEhm4Ai4jTihGnys90xY1yjXcRg==", + "dev": true + }, + "emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "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==" + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.23.0.tgz", + "integrity": "sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.3.1", + "@humanwhocodes/config-array": "^0.10.4", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@humanwhocodes/module-importer": "^1.0.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + } + } + }, + "eslint-plugin-jest": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.0.1.tgz", + "integrity": "sha512-LosUsrkwVSs/8Z/I8Hqn5vWgTEsHrfIquDEKOsV8/cl+gbFR4tiRCE1AimEotsHjSC0Rx1tYm6vPhw8C3ktmmg==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "^5.10.0" + } + }, + "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, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", + "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "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, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true + }, + "expect": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.0.2.tgz", + "integrity": "sha512-JeJlAiLKn4aApT4pzUXBVxl3NaZidWIOdg//smaIlP9ZMBDkHZGFd9ubphUZP9pUyDEo7bC6M0IIZR51o75qQw==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.0.2", + "jest-get-type": "^29.0.0", + "jest-matcher-utils": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-util": "^29.0.2" + } + }, + "express": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "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==", + "requires": { + "ee-first": "1.1.1" + } + }, + "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==" + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true + }, + "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 + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "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 + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "dev": true + }, + "filesize": { + "version": "10.0.12", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.0.12.tgz", + "integrity": "sha512-6RS9gDchbn+qWmtV2uSjo5vmKizgfCQeb5jKmqx8HyzA3MoLqqyQxN+QcjkGBJt7FjJ9qFce67Auyya5rRRbpw==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "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==", + "requires": { + "ee-first": "1.1.1" + } + } + } + }, + "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, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", + "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "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, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "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 + }, + "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 + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "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 + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "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, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "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 + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "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 + }, + "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==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-reasons": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/http-reasons/-/http-reasons-0.1.0.tgz", + "integrity": "sha512-P6kYh0lKZ+y29T2Gqz+RlC9WBLhKe8kDmcJ+A+611jFfxdPsbMRQ5aNmFRM3lENqFkK+HTTL+tlQviAiv0AbLQ==", + "dev": true + }, + "http-signature": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.14.1" + } + }, + "httpntlm": { + "version": "1.8.13", + "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.8.13.tgz", + "integrity": "sha512-2F2FDPiWT4rewPzNMg3uPhNkP3NExENlUGADRUDPQvuftuUTGW98nLZtGemCIW3G40VhWZYgkIDcQFAwZ3mf2Q==", + "dev": true, + "requires": { + "des.js": "^1.0.1", + "httpreq": ">=0.4.22", + "js-md4": "^0.3.2", + "underscore": "~1.12.1" + } + }, + "httpreq": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-1.1.1.tgz", + "integrity": "sha512-uhSZLPPD2VXXOSN8Cni3kIsoFHaU2pT/nySEU/fHr/ePbqHYr0jeiQRmUKLEirC09SFPsdMoA7LU7UXMd/w0Kw==", + "dev": true + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "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 + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "into-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", + "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", + "dev": true, + "requires": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + } + }, + "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==" + }, + "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 + }, + "is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "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, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "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 + }, + "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 + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", + "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.0.2.tgz", + "integrity": "sha512-enziNbNUmXTcTaTP/Uq5rV91r0Yqy2UKzLUIabxMpGm9YHz8qpbJhiRnNVNvm6vzWfzt/0o97NEHH8/3udoClA==", + "dev": true, + "requires": { + "@jest/core": "^29.0.2", + "@jest/types": "^29.0.2", + "import-local": "^3.0.2", + "jest-cli": "^29.0.2" + } + }, + "jest-changed-files": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.0.0.tgz", + "integrity": "sha512-28/iDMDrUpGoCitTURuDqUzWQoWmOmOKOFST1mi2lwh62X4BFf6khgH3uSuo1e49X/UDjuApAj3w0wLOex4VPQ==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "dependencies": { + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + } + } + }, + "jest-circus": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.0.2.tgz", + "integrity": "sha512-YTPEsoE1P1X0bcyDQi3QIkpt2Wl9om9k2DQRuLFdS5x8VvAKSdYAVJufgvudhnKgM8WHvvAzhBE+1DRQB8x1CQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.0.2", + "@jest/expect": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.0.2", + "jest-matcher-utils": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-runtime": "^29.0.2", + "jest-snapshot": "^29.0.2", + "jest-util": "^29.0.2", + "p-limit": "^3.1.0", + "pretty-format": "^29.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + } + } + }, + "jest-cli": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.0.2.tgz", + "integrity": "sha512-tlf8b+4KcUbBGr25cywIi3+rbZ4+G+SiG8SvY552m9sRZbXPafdmQRyeVE/C/R8K+TiBAMrTIUmV2SlStRJ40g==", + "dev": true, + "requires": { + "@jest/core": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/types": "^29.0.2", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.0.2", + "jest-util": "^29.0.2", + "jest-validate": "^29.0.2", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + } + }, + "jest-config": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.0.2.tgz", + "integrity": "sha512-RU4gzeUNZAFktYVzDGimDxeYoaiTnH100jkYYZgldqFamaZukF0IqmFx8+QrzVeEWccYg10EEJT3ox1Dq5b74w==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.0.2", + "@jest/types": "^29.0.2", + "babel-jest": "^29.0.2", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.0.2", + "jest-environment-node": "^29.0.2", + "jest-get-type": "^29.0.0", + "jest-regex-util": "^29.0.0", + "jest-resolve": "^29.0.2", + "jest-runner": "^29.0.2", + "jest-util": "^29.0.2", + "jest-validate": "^29.0.2", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.0.2", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "@jest/transform": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.0.2.tgz", + "integrity": "sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + } + }, + "babel-jest": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.0.2.tgz", + "integrity": "sha512-yTu4/WSi/HzarjQtrJSwV+/0maoNt+iP0DmpvFJdv9yY+5BuNle8TbheHzzcSWj5gIHfuhpbLYHWRDYhWKyeKQ==", + "dev": true, + "requires": { + "@jest/transform": "^29.0.2", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.0.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.0.2.tgz", + "integrity": "sha512-eBr2ynAEFjcebVvu8Ktx580BD1QKCrBG1XwEUTXJe285p9HA/4hOhfWCFRQhTKSyBV0VzjhG7H91Eifz9s29hg==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.0.2.tgz", + "integrity": "sha512-BeVXp7rH5TK96ofyEnHjznjLMQ2nAeDJ+QzxKnHAAMs0RgrQsCywjAN8m4mOm5Di0pxU//3AoEeJJrerMH5UeA==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^29.0.2", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true + }, + "jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-diff": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.0.2.tgz", + "integrity": "sha512-b9l9970sa1rMXH1owp2Woprmy42qIwwll/htsw4Gf7+WuSp5bZxNhkKHDuCGKL+HoHn1KhcC+tNEeAPYBkD2Jg==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.0.0", + "jest-get-type": "^29.0.0", + "pretty-format": "^29.0.2" + } + }, + "jest-docblock": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.0.0.tgz", + "integrity": "sha512-s5Kpra/kLzbqu9dEjov30kj1n4tfu3e7Pl8v+f8jOkeWNqM6Ds8jRaJfZow3ducoQUrf2Z4rs2N5S3zXnb83gw==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.0.2.tgz", + "integrity": "sha512-+sA9YjrJl35iCg0W0VCrgCVj+wGhDrrKQ+YAqJ/DHBC4gcDFAeePtRRhpJnX9gvOZ63G7gt52pwp2PesuSEx0Q==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "chalk": "^4.0.0", + "jest-get-type": "^29.0.0", + "jest-util": "^29.0.2", + "pretty-format": "^29.0.2" + } + }, + "jest-environment-node": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.0.2.tgz", + "integrity": "sha512-4Fv8GXVCToRlMzDO94gvA8iOzKxQ7rhAbs8L+j8GPyTxGuUiYkV+63LecGeVdVhsL2KXih1sKnoqmH6tp89J7Q==", + "dev": true, + "requires": { + "@jest/environment": "^29.0.2", + "@jest/fake-timers": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "jest-mock": "^29.0.2", + "jest-util": "^29.0.2" + } + }, + "jest-get-type": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.0.0.tgz", + "integrity": "sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw==", + "dev": true + }, + "jest-leak-detector": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.0.2.tgz", + "integrity": "sha512-5f0493qDeAxjUldkBSQg5D1cLadRgZVyWpTQvfJeQwQUpHQInE21AyVHVv64M7P2Ue8Z5EZ4BAcoDS/dSPPgMw==", + "dev": true, + "requires": { + "jest-get-type": "^29.0.0", + "pretty-format": "^29.0.2" + } + }, + "jest-matcher-utils": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.0.2.tgz", + "integrity": "sha512-s62YkHFBfAx0JLA2QX1BlnCRFwHRobwAv2KP1+YhjzF6ZCbCVrf1sG8UJyn62ZUsDaQKpoo86XMTjkUyO5aWmQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.0.2", + "jest-get-type": "^29.0.0", + "pretty-format": "^29.0.2" + } + }, + "jest-message-util": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.2.tgz", + "integrity": "sha512-kcJAgms3ckJV0wUoLsAM40xAhY+pb9FVSZwicjFU9PFkaTNmqh9xd99/CzKse48wPM1ANUQKmp03/DpkY+lGrA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.0.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-mock": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.0.2.tgz", + "integrity": "sha512-giWXOIT23UCxHCN2VUfUJ0Q7SmiqQwfSFXlCaIhW5anITpNQ+3vuLPQdKt5wkuwM37GrbFyHIClce8AAK9ft9g==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "@types/node": "*" + } + }, + "jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "requires": {} + }, + "jest-resolve": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.0.2.tgz", + "integrity": "sha512-V3uLjSA+EHxLtjIDKTBXnY71hyx+8lusCqPXvqzkFO1uCGvVpjBfuOyp+KOLBNSuY61kM2jhepiMwt4eiJS+Vw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.0.2", + "jest-validate": "^29.0.2", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "dependencies": { + "jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true + }, + "jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-resolve-dependencies": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.0.2.tgz", + "integrity": "sha512-fSAu6eIG7wtGdnPJUkVVdILGzYAP9Dj/4+zvC8BrGe8msaUMJ9JeygU0Hf9+Uor6/icbuuzQn5See1uajLnAqg==", + "dev": true, + "requires": { + "jest-regex-util": "^29.0.0", + "jest-snapshot": "^29.0.2" + }, + "dependencies": { + "jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true + } + } + }, + "jest-runner": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.0.2.tgz", + "integrity": "sha512-+D82iPZejI8t+SfduOO1deahC/QgLFf8aJBO++Znz3l2ETtOMdM7K4ATsGWzCFnTGio5yHaRifg1Su5Ybza5Nw==", + "dev": true, + "requires": { + "@jest/console": "^29.0.2", + "@jest/environment": "^29.0.2", + "@jest/test-result": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.0.0", + "jest-environment-node": "^29.0.2", + "jest-haste-map": "^29.0.2", + "jest-leak-detector": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-resolve": "^29.0.2", + "jest-runtime": "^29.0.2", + "jest-util": "^29.0.2", + "jest-watcher": "^29.0.2", + "jest-worker": "^29.0.2", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "dependencies": { + "@jest/transform": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.0.2.tgz", + "integrity": "sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + } + }, + "jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true + }, + "jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-runtime": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.0.2.tgz", + "integrity": "sha512-DO6F81LX4okOgjJLkLySv10E5YcV5NHUbY1ZqAUtofxdQE+q4hjH0P2gNsY8x3z3sqgw7O/+919SU4r18Fcuig==", + "dev": true, + "requires": { + "@jest/environment": "^29.0.2", + "@jest/fake-timers": "^29.0.2", + "@jest/globals": "^29.0.2", + "@jest/source-map": "^29.0.0", + "@jest/test-result": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-mock": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-resolve": "^29.0.2", + "jest-snapshot": "^29.0.2", + "jest-util": "^29.0.2", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "dependencies": { + "@jest/transform": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.0.2.tgz", + "integrity": "sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + } + }, + "jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true + }, + "jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-snapshot": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.0.2.tgz", + "integrity": "sha512-26C4PzGKaX5gkoKg8UzYGVy2HPVcTaROSkf0gwnHu3lGeTB7bAIJBovvVPZoiJ20IximJELQs/r8WSDRCuGX2A==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.0.2", + "@jest/transform": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.0.2", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.0.2", + "jest-get-type": "^29.0.0", + "jest-haste-map": "^29.0.2", + "jest-matcher-utils": "^29.0.2", + "jest-message-util": "^29.0.2", + "jest-util": "^29.0.2", + "natural-compare": "^1.4.0", + "pretty-format": "^29.0.2", + "semver": "^7.3.5" + }, + "dependencies": { + "@jest/transform": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.0.2.tgz", + "integrity": "sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.0.2", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.0.2", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + } + }, + "jest-haste-map": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.2.tgz", + "integrity": "sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.0.0", + "jest-util": "^29.0.2", + "jest-worker": "^29.0.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", + "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", + "dev": true + }, + "jest-worker": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.2.tgz", + "integrity": "sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-util": { + "version": "29.6.2", + "resolved": "https://registry.npmmirror.com/jest-util/-/jest-util-29.6.2.tgz", + "integrity": "sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w==", + "dev": true, + "requires": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "jest-validate": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.0.2.tgz", + "integrity": "sha512-AeRKm7cEucSy7tr54r3LhiGIXYvOILUwBM1S7jQkKs6YelwAlWKsmZGVrQR7uwsd31rBTnR5NQkODi1Z+6TKIQ==", + "dev": true, + "requires": { + "@jest/types": "^29.0.2", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.0.0", + "leven": "^3.1.0", + "pretty-format": "^29.0.2" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + } + } + }, + "jest-watcher": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.0.2.tgz", + "integrity": "sha512-ds2bV0oyUdYoyrUTv4Ga5uptz4cEvmmP/JzqDyzZZanvrIn8ipxg5l3SDOAIiyuAx1VdHd2FBzeXPFO5KPH8vQ==", + "dev": true, + "requires": { + "@jest/test-result": "^29.0.2", + "@jest/types": "^29.0.2", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^29.0.2", + "string-length": "^4.0.1" + } + }, + "jest-websocket-mock": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.4.0.tgz", + "integrity": "sha512-AOwyuRw6fgROXHxMOiTDl1/T4dh3fV4jDquha5N0csS/PNp742HeTZWPAuKppVRSQ8s3fUGgJHoyZT9JDO0hMA==", + "dev": true, + "requires": { + "jest-diff": "^28.0.2", + "mock-socket": "^9.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "dev": true + }, + "jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + } + }, + "jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true + }, + "pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "requires": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + } + } + }, + "jose": { + "version": "4.14.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", + "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", + "dev": true + }, + "js-md4": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==", + "dev": true + }, + "js-sha512": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz", + "integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ==", + "dev": true + }, + "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 + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "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 + }, + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true + }, + "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 + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "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 + }, + "liquid-json": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/liquid-json/-/liquid-json-0.3.1.tgz", + "integrity": "sha512-wUayTU8MS827Dam6MxgD72Ui+KOSF+u/eIqpatOtjnvgJ0+mnDq33uC2M7J0tPK+upe/DpUAuK4JUU89iBoNKQ==", + "dev": true + }, + "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, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + }, + "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, + "requires": { + "yallist": "^4.0.0" + } + }, + "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, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "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==" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "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 + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-format": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mime-format/-/mime-format-2.0.1.tgz", + "integrity": "sha512-XxU3ngPbEnrYnNbIX+lYSaYg0M01v6p2ntd2YaFksTu0vayaw5OJvbdRyWs07EYRlLED5qadUZ+xo+XhOvFhwg==", + "dev": true, + "requires": { + "charset": "^1.0.0" + } + }, + "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==", + "requires": { + "mime-db": "1.52.0" + } + }, + "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 + }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true + }, + "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 + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "mock-socket": { + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.1.5.tgz", + "integrity": "sha512-3DeNIcsQixWHHKk6NdoBhWI4t1VMj5/HzfnI1rE/pLl5qKx7+gd4DNA07ehTaZ6MoUU053si6Hd+YtiM/tQZfg==", + "dev": true + }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "multer": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz", + "integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "type-is": "^1.6.18" + } + }, + "multistream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/multistream/-/multistream-4.1.0.tgz", + "integrity": "sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==", + "dev": true, + "requires": { + "once": "^1.4.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "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 + }, + "newman": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/newman/-/newman-6.0.0.tgz", + "integrity": "sha512-QaANQC5b6ga348MezIVRI9ZmMs+cg3MdYIp0tSEauH2tmWOAR9+cghNsFJNjU9JPui3jJp1ALC7pQq6g3Jqpxw==", + "dev": true, + "requires": { + "@postman/tough-cookie": "4.1.3-postman.1", + "async": "3.2.4", + "chardet": "1.6.0", + "cli-progress": "3.12.0", + "cli-table3": "0.6.3", + "colors": "1.4.0", + "commander": "11.0.0", + "csv-parse": "4.16.3", + "filesize": "10.0.12", + "liquid-json": "0.3.1", + "lodash": "4.17.21", + "mkdirp": "3.0.1", + "postman-collection": "4.2.1", + "postman-collection-transformer": "4.1.7", + "postman-request": "2.88.1-postman.33", + "postman-runtime": "7.33.0", + "pretty-ms": "7.0.1", + "semver": "7.5.4", + "serialised-error": "1.1.3", + "word-wrap": "1.2.5", + "xmlbuilder": "15.1.1" + } + }, + "node-abi": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", + "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", + "dev": true, + "requires": { + "semver": "^5.4.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-oauth1": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-oauth1/-/node-oauth1-1.3.0.tgz", + "integrity": "sha512-0yggixNfrA1KcBwvh/Hy2xAS1Wfs9dcg6TdFf2zN7gilcAigMdrtZ4ybrBSXBgLvGDw9V1p2MRnGBMq7XjTWLg==", + "dev": true + }, + "node-releases": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", + "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "path-key": "^3.0.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "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==" + }, + "object-hash": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", + "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", + "dev": true + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "peer": true + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true + }, + "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, + "requires": { + "p-try": "^2.0.0" + } + }, + "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, + "requires": { + "p-limit": "^2.2.0" + } + }, + "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 + }, + "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, + "requires": { + "callsites": "^3.0.0" + } + }, + "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, + "requires": { + "@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" + } + }, + "parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "dev": true + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "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 + }, + "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==" + }, + "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 + }, + "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 + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "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 + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true + }, + "pkg": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/pkg/-/pkg-5.8.0.tgz", + "integrity": "sha512-8h9PUDYFi+LOMLbIyGRdP21g08mAtHidSpofSrf8LWhxUWGHymaRzcopEGiynB5EhQmZUKM6PQ9kCImV2TpdjQ==", + "dev": true, + "requires": { + "@babel/generator": "7.18.2", + "@babel/parser": "7.18.4", + "@babel/types": "7.18.4", + "chalk": "^4.1.2", + "fs-extra": "^9.1.0", + "globby": "^11.1.0", + "into-stream": "^6.0.0", + "is-core-module": "2.9.0", + "minimist": "^1.2.6", + "multistream": "^4.1.0", + "pkg-fetch": "3.4.2", + "prebuild-install": "6.1.4", + "resolve": "^1.22.0", + "stream-meter": "^1.0.4" + } + }, + "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, + "requires": { + "find-up": "^4.0.0" + } + }, + "pkg-fetch": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.4.2.tgz", + "integrity": "sha512-0+uijmzYcnhC0hStDjm/cl2VYdrmVVBpe7Q8k9YBojxmR5tG8mvR9/nooQq3QSXiQqORDVOTY3XqMEqJVIzkHA==", + "dev": true, + "requires": { + "chalk": "^4.1.2", + "fs-extra": "^9.1.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.6", + "progress": "^2.0.3", + "semver": "^7.3.5", + "tar-fs": "^2.1.1", + "yargs": "^16.2.0" + }, + "dependencies": { + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "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" + } + }, + "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 + } + } + }, + "postman-collection": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.2.1.tgz", + "integrity": "sha512-DFLt3/yu8+ldtOTIzmBUctoupKJBOVK4NZO0t68K2lIir9smQg7OdQTBjOXYy+PDh7u0pSDvD66tm93eBHEPHA==", + "dev": true, + "requires": { + "@faker-js/faker": "5.5.3", + "file-type": "3.9.0", + "http-reasons": "0.1.0", + "iconv-lite": "0.6.3", + "liquid-json": "0.3.1", + "lodash": "4.17.21", + "mime-format": "2.0.1", + "mime-types": "2.1.35", + "postman-url-encoder": "3.0.5", + "semver": "7.5.4", + "uuid": "8.3.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "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 + } + } + }, + "postman-collection-transformer": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/postman-collection-transformer/-/postman-collection-transformer-4.1.7.tgz", + "integrity": "sha512-SxJkm/LnlFZs2splUBnS4jQFicgBptghpm4voHtNnaum3Ad64E2MHLV4fJhv58dVUmFwdSwdQUN3m2q0iLecnQ==", + "dev": true, + "requires": { + "commander": "8.3.0", + "inherits": "2.0.4", + "lodash": "4.17.21", + "semver": "7.5.4", + "strip-json-comments": "3.1.1" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + } + } + }, + "postman-request": { + "version": "2.88.1-postman.33", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.33.tgz", + "integrity": "sha512-uL9sCML4gPH6Z4hreDWbeinKU0p0Ke261nU7OvII95NU22HN6Dk7T/SaVPaj6T4TsQqGKIFw6/woLZnH7ugFNA==", + "dev": true, + "requires": { + "@postman/form-data": "~3.1.1", + "@postman/tough-cookie": "~4.1.3-postman.1", + "@postman/tunnel-agent": "^0.6.3", + "aws-sign2": "~0.7.0", + "aws4": "^1.12.0", + "brotli": "^1.3.3", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "har-validator": "~5.1.3", + "http-signature": "~1.3.1", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "^2.1.35", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.3", + "safe-buffer": "^5.1.2", + "stream-length": "^1.0.2", + "uuid": "^8.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true + }, + "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 + } + } + }, + "postman-runtime": { + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/postman-runtime/-/postman-runtime-7.33.0.tgz", + "integrity": "sha512-cYCb+5Y12FwZU/T3gOj2SKiOz38pisVLc0tdppb+ZlG7iqn5aLgxghJwhjG62pZCV6uixKiQX1hNdLSk9a9Xtw==", + "dev": true, + "requires": { + "@postman/tough-cookie": "4.1.3-postman.1", + "async": "3.2.4", + "aws4": "1.12.0", + "handlebars": "4.7.8", + "httpntlm": "1.8.13", + "jose": "4.14.4", + "js-sha512": "0.8.0", + "lodash": "4.17.21", + "mime-types": "2.1.35", + "node-oauth1": "1.3.0", + "performance-now": "2.1.0", + "postman-collection": "4.2.0", + "postman-request": "2.88.1-postman.33", + "postman-sandbox": "4.2.7", + "postman-url-encoder": "3.0.5", + "serialised-error": "1.1.3", + "strip-json-comments": "3.1.1", + "uuid": "8.3.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "postman-collection": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.2.0.tgz", + "integrity": "sha512-tvOLgN1h6Kab6dt43PmBoV5kYO/YUta3x0C2QqfmbzmHZe47VTpZ/+gIkGlbNhjKNPUUub5X6ehxYKoaTYdy1w==", + "dev": true, + "requires": { + "@faker-js/faker": "5.5.3", + "file-type": "3.9.0", + "http-reasons": "0.1.0", + "iconv-lite": "0.6.3", + "liquid-json": "0.3.1", + "lodash": "4.17.21", + "mime-format": "2.0.1", + "mime-types": "2.1.35", + "postman-url-encoder": "3.0.5", + "semver": "7.5.4", + "uuid": "8.3.2" + } + }, + "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 + } + } + }, + "postman-sandbox": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/postman-sandbox/-/postman-sandbox-4.2.7.tgz", + "integrity": "sha512-/EcCrKnb/o+9iLS4u+H76E0kBomJFjPptVjoDiq1uZ7Es/4aTv0MAX+0aoDxdDO+0h9sl8vy65uKQwyjN7AOaw==", + "dev": true, + "requires": { + "lodash": "4.17.21", + "postman-collection": "4.2.0", + "teleport-javascript": "1.0.0", + "uvm": "2.1.1" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "postman-collection": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.2.0.tgz", + "integrity": "sha512-tvOLgN1h6Kab6dt43PmBoV5kYO/YUta3x0C2QqfmbzmHZe47VTpZ/+gIkGlbNhjKNPUUub5X6ehxYKoaTYdy1w==", + "dev": true, + "requires": { + "@faker-js/faker": "5.5.3", + "file-type": "3.9.0", + "http-reasons": "0.1.0", + "iconv-lite": "0.6.3", + "liquid-json": "0.3.1", + "lodash": "4.17.21", + "mime-format": "2.0.1", + "mime-types": "2.1.35", + "postman-url-encoder": "3.0.5", + "semver": "7.5.4", + "uuid": "8.3.2" + } + }, + "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 + } + } + }, + "postman-url-encoder": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-3.0.5.tgz", + "integrity": "sha512-jOrdVvzUXBC7C+9gkIkpDJ3HIxOHTIqjpQ4C1EMt1ZGeMvSEpbFCKq23DEfgsj46vMnDgyQf+1ZLp2Wm+bKSsA==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "prebuild-install": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", + "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", + "dev": true, + "requires": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.21.0", + "npmlog": "^4.0.1", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "pretty-format": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.2.tgz", + "integrity": "sha512-wp3CdtUa3cSJVFn3Miu5a1+pxc1iPIQTenOAn+x5erXeN1+ryTcLesV5pbK/rlW5EKwp27x38MoYfNGaNXDDhg==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "dev": true, + "requires": { + "parse-ms": "^2.1.0" + } + }, + "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 + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "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==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "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 + }, + "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==" + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "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" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "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 + }, + "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 + }, + "resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "requires": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "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 + }, + "resolve.exports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", + "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "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, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "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==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "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" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "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==", + "requires": { + "ee-first": "1.1.1" + } + } + } + }, + "serialised-error": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/serialised-error/-/serialised-error-1.1.3.tgz", + "integrity": "sha512-vybp3GItaR1ZtO2nxZZo8eOo7fnVaNtP3XE2vJKgzkKR2bagCkdJ1EpYYhEMd3qu/80DwQk9KjsNSxE3fXWq0g==", + "dev": true, + "requires": { + "object-hash": "^1.1.2", + "stack-trace": "0.0.9", + "uuid": "^3.0.0" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "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, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "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 + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "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 + }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true + }, + "simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dev": true, + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-trace": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", + "integrity": "sha512-vjUc6sfgtgY0dxCdnc40mK6Oftjo9+2K8H/NG81TMhgL392FtiPA9tn9RLyTxXmTLPJPjF3VyzFp6bsWFLisMQ==", + "dev": true + }, + "stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "stream-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz", + "integrity": "sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg==", + "dev": true, + "requires": { + "bluebird": "^2.6.2" + } + }, + "stream-meter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stream-meter/-/stream-meter-1.0.4.tgz", + "integrity": "sha1-Uq+Vql6nYKJJFxZwTb/5D3Ov3R0=", + "dev": true, + "requires": { + "readable-stream": "^2.1.4" + } + }, + "streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" + }, + "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==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "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, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "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 + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + } + }, + "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 + }, + "swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "requires": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "dependencies": { + "commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "requires": { + "@apidevtools/swagger-parser": "10.0.3" + } + }, + "swagger-ui-dist": { + "version": "5.32.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.0.tgz", + "integrity": "sha512-nKZB0OuDvacB0s/lC2gbge+RigYvGRGpLLMWMFxaTUwfM+CfndVk9Th2IaTinqXiz6Mn26GK2zriCpv6/+5m3Q==", + "requires": { + "@scarf/scarf": "=1.4.0" + } + }, + "swagger-ui-express": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.3.tgz", + "integrity": "sha512-CDje4PndhTD2HkgyKH3pab+LKspDeB/NhPN2OF1j+piYIamQqBYwAXWESOT1Yju2xFg51bRW9sUng2WxDjzArw==", + "requires": { + "swagger-ui-dist": ">=4.11.0" + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "teleport-javascript": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/teleport-javascript/-/teleport-javascript-1.0.0.tgz", + "integrity": "sha512-j1llvWVFyEn/6XIFDfX5LAU43DXe0GCt3NfXDwJ8XpRRMkS+i50SAkonAONBy+vxwPFBd50MFU8a2uj8R/ccLg==", + "dev": true + }, + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "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, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "ts-jest": { + "version": "29.1.1", + "resolved": "https://registry.npmmirror.com/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", + "dev": true, + "requires": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + } + }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, + "typescript": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz", + "integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==", + "dev": true + }, + "uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "optional": true + }, + "underscore": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", + "dev": true + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + }, + "uvm": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/uvm/-/uvm-2.1.1.tgz", + "integrity": "sha512-BZ5w8adTpNNr+zczOBRpaX/hH8UPKAf7fmCnidrcsqt3bn8KT9bDIfuS7hgRU9RXgiN01su2pwysBONY6w8W5w==", + "dev": true, + "requires": { + "flatted": "3.2.6" + } + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "v8-to-istanbul": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", + "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + } + }, + "validator": { + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true + } + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "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 + }, + "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, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.1.tgz", + "integrity": "sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "requires": {} + }, + "xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==" + }, + "yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + }, + "z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "requires": { + "commander": "^9.4.1", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "dependencies": { + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..31864d7 --- /dev/null +++ b/package.json @@ -0,0 +1,59 @@ +{ + "name": "webserver", + "version": "3.1.0", + "private": true, + "scripts": { + "build": "tsc -p tsconfig.build.json", + "test": "jest --colors test/*.ts", + "newman": "newman run test/renderstreaming.postman_collection.json", + "start": "node ./build/index.js -s -p 8080 -m private -k ./server.key -c ./server.cert", + "dev": "ts-node ./src/index.ts -s -p 8080 -m private", + "lint": "eslint src/**/*.ts test/**/*.ts", + "pack": "pkg ." + }, + "dependencies": { + "@types/express": "^4.17.13", + "@types/node": "^18.7.15", + "@types/ws": "^8.5.3", + "cors": "^2.8.5", + "debug": "~4.3.4", + "express": "~4.18.1", + "morgan": "^1.10.0", + "multer": "^2.1.1", + "swagger-jsdoc": "^6.2.1", + "swagger-ui-express": "^4.5.0", + "uuid": "^9.0.0", + "ws": "^8.8.1" + }, + "devDependencies": { + "@jest-mock/express": "^2.0.1", + "@types/jest": "^29.0.2", + "@types/morgan": "^1.9.3", + "@types/swagger-jsdoc": "^6.0.1", + "@types/swagger-ui-express": "^4.1.3", + "@typescript-eslint/eslint-plugin": "^5.36.2", + "@typescript-eslint/parser": "^5.36.2", + "eslint": "^8.23.0", + "eslint-plugin-jest": "^27.0.1", + "jest": "^29.0.2", + "jest-websocket-mock": "^2.4.0", + "mock-socket": "^9.1.5", + "newman": "^6.0.0", + "pkg": "^5.8.0", + "ts-jest": "^29.0.2", + "ts-node": "^10.9.1", + "typescript": "^4.8.2" + }, + "bin": { + "webserver": "build/index.js" + }, + "pkg": { + "assets": [ + "client/public/**/*", + "client/src/**/*" + ], + "targets": [ + "node10" + ] + } +} diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..36fb672 --- /dev/null +++ b/run.bat @@ -0,0 +1,16 @@ +@echo off +pushd %~dp0 +call npm run build +call npm run start +popd +pause + +node ./build/index.js -s -p 8080 -m private -k ./server.key -c ./server.cert -l dev + + +// 通话测试代码 +const caller = { + name: "Sarah Chen", + avatar: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100&h=100&fit=crop" +}; +window.showCallRequest(caller); diff --git a/server.cert b/server.cert new file mode 100644 index 0000000..9c2963a --- /dev/null +++ b/server.cert @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkzCCAnugAwIBAgIUUSy8jVg4AA8orKk80GBpbe+YMDIwDQYJKoZIhvcNAQEL +BQAwcjELMAkGA1UEBhMCQ0gxCzAJBgNVBAgMAlpKMQswCQYDVQQHDAJIWjEOMAwG +A1UECgwFU1RBUlkxCzAJBgNVBAsMAktGMQswCQYDVQQDDAJaWjEfMB0GCSqGSIb3 +DQEJARYQODM0MjA3MTcyQHFxLmNvbTAeFw0yNjAyMjcwODExMTVaFw0yNzAyMjcw +ODExMTVaMHIxCzAJBgNVBAYTAkNIMQswCQYDVQQIDAJaSjELMAkGA1UEBwwCSFox +DjAMBgNVBAoMBVNUQVJZMQswCQYDVQQLDAJLRjELMAkGA1UEAwwCWloxHzAdBgkq +hkiG9w0BCQEWEDgzNDIwNzE3MkBxcS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQC68fDdstoTwmXca0EG28xQ8s2cp24Mr9rPUyPMsbtJiDtOf4uD +/mUqybl0YNeAIWm7jhwOaxAidl1Y6+J8o6FEX+EGu1qgldP0wV9C/OqpedBMULpt +X7BkkAYsHpa3WmV8AZC3B61xng/jVyEj1rLrzMn+2yoC/VZEH41jE9wGPvL3qL5J +80Ssxmly4NEwygxtr8cXbhm2+UFudevvtW5QAOBbzeAt2mEPfRZNA4NMYkao/9vQ +MZpYEYnY2hO9KxYaHXoQ0rebt/vXJgrndNn3sMCuwePWCIFnQM3lNV6y+bAX833G +i0eYYWAlQ7PzKDDh8oK1IYrZF9ebSojc1r41AgMBAAGjITAfMB0GA1UdDgQWBBQO +SK56uQTrNF7UeNyrBdbCihh2YTANBgkqhkiG9w0BAQsFAAOCAQEAOpfrvwjhqGsT +lCpA2ntDC0AI++Vm2FDFBc4TpY3ZJFTj/PJ3sB4Dbh4KB57Nafwdpe4Clp4TVB9y +H5v4EQemMgtswGzByrAjSIsbN41kYFobJBi9bHLIERO7OhAQRkmeqJAuWx978SB1 +lm2AUhd7+Y3lDoCpaqSXB8bPqrigpjfGhwhZTE6PLBBjIc79rbXsJUL02Xguwb+P +Ertnm5TY62xEz7NamwVwAAy8wvVxxKftkqo2UEQJ0WIs20cJMtZRkbzHPAwFCXRY +Hov1YCto5Yr4kxWwXrLuOcaRp1hvp3jBCeIRjeQ0oDb9uwbaQPmkmjIzKehVFT+B +cB/Vf5yjmA== +-----END CERTIFICATE----- diff --git a/server.key b/server.key new file mode 100644 index 0000000..9a69299 --- /dev/null +++ b/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC68fDdstoTwmXc +a0EG28xQ8s2cp24Mr9rPUyPMsbtJiDtOf4uD/mUqybl0YNeAIWm7jhwOaxAidl1Y +6+J8o6FEX+EGu1qgldP0wV9C/OqpedBMULptX7BkkAYsHpa3WmV8AZC3B61xng/j +VyEj1rLrzMn+2yoC/VZEH41jE9wGPvL3qL5J80Ssxmly4NEwygxtr8cXbhm2+UFu +devvtW5QAOBbzeAt2mEPfRZNA4NMYkao/9vQMZpYEYnY2hO9KxYaHXoQ0rebt/vX +JgrndNn3sMCuwePWCIFnQM3lNV6y+bAX833Gi0eYYWAlQ7PzKDDh8oK1IYrZF9eb +Sojc1r41AgMBAAECggEALMiiQr3ie6rNc4r3CjFT5g1wX6eFV8E16gY49IOcQe6C +hJjN34otCp3GEWDUpzN57I4L3xFbTWItFQz3FhQt4R8V3WK7AG68Fq1PSJEllwFh +OrL5IhPzAfg1nlKamORtHNKULCCI9oeetz+rZvY7KP1HY4f1rb8qMAod+P7sLctz +9fYfpIRGYRU8TjZK+VvL47lQor4227CXWucveOHDh4rbQ2RSklzyqfG13q2QosiM +C+BaV7U3RmFt9vBagQApFRJitspysIDt+l+9crywyXvUJ73FBcdywHempKctzHAX +cipXA4503gSm/2Em7s3xSJSu4xLq07xfcAlOj/bNUQKBgQDvNcgzgdfpxowbwDdj +YQLL4QP+dyqZo9brPAZSUR3vXiFf0ETPSmkEfrAorI230okjSdmYXDF2IY9YmOdD +G/VS9ziNQ5EOHQlO/Z79ZsJGlEvW7dgigym/QmlSQtXQJLlZMHFcmcOFNhxOwEVU +4efZpcBxS8OW6kGDp6KrjT/AsQKBgQDIEQqU0ukyCfCoyLXC6xUWvZgE4C+EAYyH +XBOi5YG7n2D6nJs8Sr66n9pdAsH+6mruzWqS6UbAMcjegMvbV1AJurQbvWJQrlSg +Krcx7CsIIuXlMvAkj6nXQemuxechNsRz1+DheziEOMVOYN6vCF3ZXmoMCNnX5G5E +tx6sI8xWxQKBgBujRyJMWjn0arO6HawpxTzxHpkEA7QErgw3vzM7lA3X/lsnoQui +OUf/qzLH3QfkF1wwaCemX0Ca2AkQgYfLbyjKz4niyJus1yp9OyVj/gZRlAGdMV+r +b+NdNPYAKtwFojSf2DkuutxlSBgBNwF7qGIJOJbefhatsiwX7j+L7lXBAoGBAK++ +2I63X4ZHWRLLjW92VoYtcjcGQHczbSES72TvOzF35hcALxWdi0IhXg7Kd2PYxKqV +5AK9zRrUIOHkPi+l2XdSfRjJGm5JVamFHHxMO9jf3xeh0XYshUPEcOTIkCqLE7O3 +daJdPd5YiGo7iiIztU1XNMzxkXum/H58ZgNNRxkJAoGBAKyf60tE6tYj/R+uG7pY +YqSQAFelAp5ayzW2UR/gF8deM9MOSpAxzYL286OnWQ4UJOzSC5pVsfo7gx90en3F +38e2cgYjaP3VpL3U5+jm4cvMNKk1fOBaLqEH6UZD+66YmF7jag8CKnvhz83lN1w0 +FSbqtfFjjtVUwD0yqPUWwpsi +-----END PRIVATE KEY----- diff --git a/src/class/answer.ts b/src/class/answer.ts new file mode 100644 index 0000000..1de32df --- /dev/null +++ b/src/class/answer.ts @@ -0,0 +1,8 @@ +export default class Answer { + sdp: string; + datetime: number; + constructor(sdp: string, datetime: number) { + this.sdp = sdp; + this.datetime = datetime; + } +} \ No newline at end of file diff --git a/src/class/candidate.ts b/src/class/candidate.ts new file mode 100644 index 0000000..84c047c --- /dev/null +++ b/src/class/candidate.ts @@ -0,0 +1,12 @@ +export default class Candidate { + candidate: string; + sdpMLineIndex: number; + sdpMid: string; + datetime: number; + constructor(candidate: string, sdpMLineIndex: number, sdpMid: string, datetime: number) { + this.candidate = candidate; + this.sdpMLineIndex = sdpMLineIndex; + this.sdpMid = sdpMid; + this.datetime = datetime; + } +} \ No newline at end of file diff --git a/src/class/httphandler.ts b/src/class/httphandler.ts new file mode 100644 index 0000000..c5b65b0 --- /dev/null +++ b/src/class/httphandler.ts @@ -0,0 +1,1128 @@ +/** + * HTTP处理器 + * 负责处理HTTP请求,管理会话和连接,处理信令消息 + */ +import { Request, Response } from 'express'; +import Offer from './offer'; +import Answer from './answer'; +import Candidate from './candidate'; +import { v4 as uuid } from 'uuid'; +import { onGetAllConnectionIds } from './websockethandler'; +/** + * 断开连接记录类 + * 用于记录断开连接的信息 + */ +class Disconnection { + id: string; // 连接ID + datetime: number; // 断开连接的时间戳 + + /** + * 构造函数 + * @param id 连接ID + * @param datetime 断开连接的时间戳 + */ + constructor(id: string, datetime: number) { + this.id = id; + this.datetime = datetime; + } +} + +// 会话超时时间(毫秒) +const TimeoutRequestedTime = 10000; // 10sec + +// 是否为私有模式 +let isPrivate: boolean; + +/** + * 客户端会话映射 + * 键: 会话ID + * 值: 该会话的连接ID集合 + */ +const clients: Map> = new Map>(); + +/** + * 会话最后请求时间映射 + * 键: 会话ID + * 值: 最后请求的时间戳 + */ +const lastRequestedTime: Map = new Map(); + +/** + * 连接对映射 + * 键: 连接ID + * 值: [会话ID1, 会话ID2] + */ +const connectionPair: Map = new Map(); // key = connectionId + +/** + * 会话的offer映射 + * 键: 会话ID + * 值: 该会话的连接ID到Offer对象的映射 + */ +const offers: Map> = new Map>(); // key = sessionId + +/** + * 会话的answer映射 + * 键: 会话ID + * 值: 该会话的连接ID到Answer对象的映射 + */ +const answers: Map> = new Map>(); // key = sessionId + +/** + * 会话的candidate映射 + * 键: 会话ID + * 值: 该会话的连接ID到Candidate数组的映射 + */ +const candidates: Map> = new Map>(); // key = sessionId + +/** + * 会话的断开连接记录映射 + * 键: 会话ID + * 值: 该会话的Disconnection对象数组 + */ +const disconnections: Map = new Map(); // key = sessionId + +/** + * 获取或创建会话的连接ID集合 + * @param sessionId 会话ID + * @returns 连接ID的Set集合 + */ +function getOrCreateConnectionIds(sessionId: string): Set { + let connectionIds = null; + // 检查会话是否已存在 + if (!clients.has(sessionId)) { + // 如果不存在,创建新的连接ID集合 + connectionIds = new Set(); + // 将新的连接ID集合与会话关联 + clients.set(sessionId, connectionIds); + } + // 获取会话的连接ID集合 + connectionIds = clients.get(sessionId); + // 返回连接ID集合 + return connectionIds; +} + +/** + * 重置处理器状态 + * @param mode 通信模式(public或private) + */ +function reset(mode: string): void { + // 设置是否为私有模式 + isPrivate = mode == "private"; + // 清空所有映射 + clients.clear(); + connectionPair.clear(); + offers.clear(); + answers.clear(); + candidates.clear(); + disconnections.clear(); +} + +/** + * 检查会话ID是否有效 + * @param req Express请求对象 + * @param res Express响应对象 + * @param next 下一个中间件函数 + */ +function checkSessionId(req: Request, res: Response, next): void { + // 如果是根路径,直接通过 + if (req.url === '/') { + next(); + return; + } + // 从请求头获取会话ID + const id: string = req.header('session-id'); + // 检查会话是否存在 + if (!clients.has(id)) { + res.sendStatus(404); + return; + } + // 更新会话的最后请求时间 + lastRequestedTime.set(id, Date.now()); + // 继续处理请求 + next(); +} + +/** + * 删除连接 + * @param sessionId 会话ID + * @param connectionId 连接ID + * @param datetime 时间戳 + */ +function _deleteConnection(sessionId:string, connectionId:string, datetime:number) { + // 从会话的连接ID集合中删除连接ID + clients.get(sessionId).delete(connectionId); + + // 处理私有模式 + if(isPrivate) { + if (connectionPair.has(connectionId)) { + const pair = connectionPair.get(connectionId); + // 找到另一个会话ID + const otherSessionId = pair[0] == sessionId ? pair[1] : pair[0]; + if (otherSessionId) { + if (clients.has(otherSessionId)) { + // 从另一个会话的连接ID集合中删除连接ID + clients.get(otherSessionId).delete(connectionId); + // 向另一个会话的断开连接记录中添加记录 + const array1 = disconnections.get(otherSessionId); + array1.push(new Disconnection(connectionId, datetime)); + } + } + } + } else { + // 公共模式:向所有其他会话的断开连接记录中添加记录 + disconnections.forEach((array, id) => { + if (id == sessionId) + return; + array.push(new Disconnection(connectionId, datetime)); + }); + } + + // 从连接对映射中删除 + connectionPair.delete(connectionId); + // 从会话的offer映射中删除 + offers.get(sessionId).delete(connectionId); + // 从会话的answer映射中删除 + answers.get(sessionId).delete(connectionId); + // 从会话的candidate映射中删除 + candidates.get(sessionId).delete(connectionId); + + // 向当前会话的断开连接记录中添加记录 + const array2 = disconnections.get(sessionId); + array2.push(new Disconnection(connectionId, datetime)); +} + +/** + * 删除会话 + * @param sessionId 会话ID + */ +function _deleteSession(sessionId: string) { + // 如果会话存在,删除其所有连接 + if(clients.has(sessionId)) { + for(const connectionId of Array.from(clients.get(sessionId))) { + _deleteConnection(sessionId, connectionId, Date.now()); + } + } + // 从所有映射中删除会话 + offers.delete(sessionId); + answers.delete(sessionId); + candidates.delete(sessionId); + clients.delete(sessionId); + disconnections.delete(sessionId); +} + +/** + * 检查超时会话 + */ +function _checkForTimedOutSessions(): void { + // 遍历所有会话 + for (const sessionId of Array.from(clients.keys())) + { + // 如果会话没有最后请求时间,跳过 + if(!lastRequestedTime.has(sessionId)) + continue; + // 如果会话未超时,跳过 + if(lastRequestedTime.get(sessionId) > Date.now() - TimeoutRequestedTime) + continue; + // 删除超时会话 + _deleteSession(sessionId); + console.log(`deleted sessionId:${sessionId} by timeout.`); + } +} + +/** + * 获取会话的连接ID列表 + * @param sessionId 会话ID + * @returns 连接ID数组 + */ +function _getConnection(sessionId: string): string[] { + // 检查超时会话 + _checkForTimedOutSessions(); + // 返回会话的连接ID集合的数组形式 + return Array.from(clients.get(sessionId)); +} + +/** + * 获取会话的断开连接记录 + * @param sessionId 会话ID + * @param fromTime 起始时间戳 + * @returns 断开连接记录数组 + */ +function _getDisconnection(sessionId: string, fromTime: number): Disconnection[] { + // 检查超时会话 + _checkForTimedOutSessions(); + let arrayDisconnections: Disconnection[] = []; + // 如果断开连接记录存在,获取该会话的断开连接记录 + if (disconnections.size != 0 && disconnections.has(sessionId)) { + arrayDisconnections = disconnections.get(sessionId); + } + + // 如果指定了起始时间,过滤出时间戳大于等于起始时间的记录 + if (fromTime > 0) { + arrayDisconnections = arrayDisconnections.filter((v) => v.datetime >= fromTime); + } + return arrayDisconnections; +} + +/** + * 获取会话的offer列表 + * @param sessionId 会话ID + * @param fromTime 起始时间戳 + * @returns [连接ID, Offer]数组 + */ +function _getOffer(sessionId: string, fromTime: number): [string, Offer][] { + let arrayOffers: [string, Offer][] = []; + + // 如果offer映射不为空 + if (offers.size != 0) { + // 处理私有模式 + if (isPrivate) { + // 如果会话存在offer记录,获取该会话的offer列表 + if (offers.has(sessionId)) { + arrayOffers = Array.from(offers.get(sessionId)); + } + } else { + // 公共模式:获取所有其他会话的offer列表 + const otherSessionMap = Array.from(offers).filter(x => x[0] != sessionId); + arrayOffers = [].concat(...Array.from(otherSessionMap, x => Array.from(x[1], y => [y[0], y[1]]))); + } + } + + // 如果指定了起始时间,过滤出时间戳大于等于起始时间的offer + if (fromTime > 0) { + arrayOffers = arrayOffers.filter((v) => v[1].datetime >= fromTime); + } + return arrayOffers; +} + +/** + * 获取会话的answer列表 + * @param sessionId 会话ID + * @param fromTime 起始时间戳 + * @returns [连接ID, Answer]数组 + */ +function _getAnswer(sessionId: string, fromTime: number): [string, Answer][] { + let arrayAnswers: [string, Answer][] = []; + + // 如果answer映射不为空且会话存在answer记录,获取该会话的answer列表 + if (answers.size != 0 && answers.has(sessionId)) { + arrayAnswers = Array.from(answers.get(sessionId)); + } + + // 如果指定了起始时间,过滤出时间戳大于等于起始时间的answer + if (fromTime > 0) { + arrayAnswers = arrayAnswers.filter((v) => v[1].datetime >= fromTime); + } + return arrayAnswers; +} + +/** + * 获取会话的candidate列表 + * @param sessionId 会话ID + * @param fromTime 起始时间戳 + * @returns [连接ID, Candidate]数组 + */ +function _getCandidate(sessionId: string, fromTime: number): [string, Candidate][] { + // 获取会话的连接ID列表 + const connectionIds = Array.from(clients.get(sessionId)); + const arr: [string, Candidate][] = []; + // 遍历每个连接ID + for (const connectionId of connectionIds) { + // 获取连接对 + const pair = connectionPair.get(connectionId); + if (pair == null) { + continue; + } + // 找到另一个会话ID + const otherSessionId = sessionId === pair[0] ? pair[1] : pair[0]; + // 如果另一个会话不存在candidate记录或该连接ID不存在candidate记录,跳过 + if (!candidates.get(otherSessionId) || !candidates.get(otherSessionId).get(connectionId)) { + continue; + } + // 获取该连接ID的candidate列表,并过滤出时间戳大于等于起始时间的candidate + const arrayCandidates = candidates.get(otherSessionId).get(connectionId) + .filter((v) => v.datetime >= fromTime); + // 如果没有符合条件的candidate,跳过 + if (arrayCandidates.length === 0) { + continue; + } + // 将符合条件的candidate添加到结果数组中 + for (const candidate of arrayCandidates) { + arr.push([connectionId, candidate]); + } + } + return arr; +} + +/** + * @swagger + * /signaling/answer: + * get: + * summary: 获取answer列表 + * description: 获取当前会话的answer信令消息列表 + * security: + * - sessionAuth: [] + * parameters: + * - in: query + * name: fromtime + * schema: + * type: number + * description: 起始时间戳,用于过滤消息 + * responses: + * 200: + * description: 成功获取answer列表 + * content: + * application/json: + * schema: + * type: object + * properties: + * answers: + * type: array + * items: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * sdp: + * type: string + * description: SDP描述 + * type: + * type: string + * description: 消息类型 + * datetime: + * type: number + * description: 时间戳 + */ +function getAnswer(req: Request, res: Response): void { + // 从请求查询参数中获取`fromtime`参数 + const fromTime: number = req.query.fromtime ? Number(req.query.fromtime) : 0; + // 从请求头获取会话ID + const sessionId: string = req.header('session-id'); + // 获取会话的answer列表 + const answers: [string, Answer][] = _getAnswer(sessionId, fromTime); + // 返回JSON响应,包含answer列表 + res.json({ answers: answers.map((v) => ({ connectionId: v[0], sdp: v[1].sdp, type: "answer", datetime: v[1].datetime })) }); +} + +/** + * @swagger + * /signaling/connection: + * get: + * summary: 获取连接列表 + * description: 获取当前会话的连接列表 + * security: + * - sessionAuth: [] + * responses: + * 200: + * description: 成功获取连接列表 + * content: + * application/json: + * schema: + * type: object + * properties: + * connections: + * type: array + * items: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * type: + * type: string + * description: 消息类型 + * datetime: + * type: number + * description: 时间戳 + */ +function getConnection(req: Request, res: Response): void { + // 从请求头获取会话ID + const sessionId: string = req.header('session-id'); + // 获取会话的连接ID列表 + const connections = _getConnection(sessionId); + // 返回JSON响应,包含连接列表 + res.json({ connections: connections.map((v) => ({ connectionId: v, type: "connect", datetime: Date.now() })) }); +} + +/** + * @swagger + * /signaling/offer: + * get: + * summary: 获取offer列表 + * description: 获取当前会话的offer信令消息列表 + * security: + * - sessionAuth: [] + * parameters: + * - in: query + * name: fromtime + * schema: + * type: number + * description: 起始时间戳,用于过滤消息 + * responses: + * 200: + * description: 成功获取offer列表 + * content: + * application/json: + * schema: + * type: object + * properties: + * offers: + * type: array + * items: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * sdp: + * type: string + * description: SDP描述 + * polite: + * type: boolean + * description: 是否为polite模式 + * type: + * type: string + * description: 消息类型 + * datetime: + * type: number + * description: 时间戳 + */ +function getOffer(req: Request, res: Response): void { + // 从请求查询参数中获取`fromtime`参数 + const fromTime: number = req.query.fromtime ? Number(req.query.fromtime) : 0; + // 从请求头获取会话ID + const sessionId: string = req.header('session-id'); + // 获取会话的offer列表 + const offers = _getOffer(sessionId, fromTime); + // 返回JSON响应,包含offer列表 + res.json({ offers: offers.map((v) => ({ connectionId: v[0], sdp: v[1].sdp, polite: v[1].polite, type: "offer", datetime: v[1].datetime })) }); +} + +/** + * @swagger + * /signaling/candidate: + * get: + * summary: 获取candidate列表 + * description: 获取当前会话的candidate信令消息列表 + * security: + * - sessionAuth: [] + * parameters: + * - in: query + * name: fromtime + * schema: + * type: number + * description: 起始时间戳,用于过滤消息 + * responses: + * 200: + * description: 成功获取candidate列表 + * content: + * application/json: + * schema: + * type: object + * properties: + * candidates: + * type: array + * items: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * candidate: + * type: string + * description: ICE候选者信息 + * sdpMLineIndex: + * type: number + * description: SDP媒体行索引 + * sdpMid: + * type: string + * description: SDP媒体ID + * type: + * type: string + * description: 消息类型 + * datetime: + * type: number + * description: 时间戳 + */ +function getCandidate(req: Request, res: Response): void { + // 从请求查询参数中获取`fromtime`参数 + const fromTime: number = req.query.fromtime ? Number(req.query.fromtime) : 0; + // 从请求头获取会话ID + const sessionId: string = req.header('session-id'); + // 获取会话的candidate列表 + const candidates = _getCandidate(sessionId, fromTime); + // 返回JSON响应,包含candidate列表 + res.json({ candidates: candidates.map((v) => ({ connectionId: v[0], candidate: v[1].candidate, sdpMLineIndex: v[1].sdpMLineIndex, sdpMid: v[1].sdpMid, type: "candidate", datetime: v[1].datetime })) }); +} + +/** + * @swagger + * /signaling: + * get: + * summary: 获取所有信令消息 + * description: 获取当前会话的所有信令消息,包括连接、断开连接、offer、answer和candidate + * security: + * - sessionAuth: [] + * parameters: + * - in: query + * name: fromtime + * schema: + * type: number + * description: 起始时间戳,用于过滤消息 + * responses: + * 200: + * description: 成功获取所有信令消息 + * content: + * application/json: + * schema: + * type: object + * properties: + * messages: + * type: array + * items: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * type: + * type: string + * description: 消息类型 + * datetime: + * type: number + * description: 时间戳 + * sdp: + * type: string + * description: SDP描述(仅offer和answer消息) + * polite: + * type: boolean + * description: 是否为polite模式(仅offer消息) + * candidate: + * type: string + * description: ICE候选者信息(仅candidate消息) + * sdpMLineIndex: + * type: number + * description: SDP媒体行索引(仅candidate消息) + * sdpMid: + * type: string + * description: SDP媒体ID(仅candidate消息) + * datetime: + * type: number + * description: 当前时间戳 + */ +function getAll(req: Request, res: Response): void { + // 从请求查询参数中获取`fromtime`参数 + const fromTime: number = req.query.fromtime ? Number(req.query.fromtime) : 0; + // 从请求头获取会话ID + const sessionId: string = req.header('session-id'); + // 获取各种信令消息 + const connections = _getConnection(sessionId); + const offers = _getOffer(sessionId, fromTime); + const answers: [string, Answer][] = _getAnswer(sessionId, fromTime); + const candidates: [string, Candidate][] = _getCandidate(sessionId, fromTime); + const disconnections: Disconnection[] = _getDisconnection(sessionId, fromTime); + const datetime = lastRequestedTime.get(sessionId); + + let array: any[] = []; + + // 合并所有信令消息 + array = array.concat(connections.map((v) => ({ connectionId: v, type: "connect", datetime: datetime }))); + array = array.concat(offers.map((v) => ({ connectionId: v[0], sdp: v[1].sdp, polite: v[1].polite, type: "offer", datetime: v[1].datetime }))); + array = array.concat(answers.map((v) => ({ connectionId: v[0], sdp: v[1].sdp, type: "answer", datetime: v[1].datetime }))); + array = array.concat(candidates.map((v) => ({ connectionId: v[0], candidate: v[1].candidate, sdpMLineIndex: v[1].sdpMLineIndex, sdpMid: v[1].sdpMid, type: "candidate", datetime: v[1].datetime }))); + array = array.concat(disconnections.map((v) => ({ connectionId: v.id, type: "disconnect", datetime: v.datetime }))); + + // 按时间戳排序 + array.sort((a, b) => a.datetime - b.datetime); + // 返回JSON响应,包含所有信令消息 + res.json({ messages: array, datetime: datetime }); +} + +/** + * @swagger + * /signaling: + * put: + * summary: 创建会话 + * description: 创建一个新的会话,并返回会话ID + * responses: + * 200: + * description: 成功创建会话 + * content: + * application/json: + * schema: + * type: object + * properties: + * sessionId: + * type: string + * description: 新创建的会话ID + */ +function createSession(sessionId: string, res: Response): void; +function createSession(req: Request, res: Response): void; + +function createSession(req: string | Request, res: Response): void { + // 确定会话ID + const sessionId: string = typeof req === "string" ? req : uuid(); + // 为会话创建各种映射 + clients.set(sessionId, new Set()); + offers.set(sessionId, new Map()); + answers.set(sessionId, new Map()); + candidates.set(sessionId, new Map()); + disconnections.set(sessionId, []); + // 返回JSON响应,包含会话ID + res.json({ sessionId: sessionId }); +} + +/** + * @swagger + * /signaling: + * delete: + * summary: 删除会话 + * description: 删除当前会话及其所有连接 + * security: + * - sessionAuth: [] + * responses: + * 200: + * description: 成功删除会话 + */ +function deleteSession(req: Request, res: Response): void { + // 从请求头获取会话ID + const id: string = req.header('session-id'); + // 删除会话 + _deleteSession(id); + // 返回200状态码表示请求处理成功 + res.sendStatus(200); +} + +/** + * @swagger + * /signaling/connection: + * put: + * summary: 创建连接 + * description: 创建一个新的连接,并返回连接信息 + * security: + * - sessionAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * responses: + * 200: + * description: 成功创建连接 + * content: + * application/json: + * schema: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * polite: + * type: boolean + * description: 是否为polite模式 + * type: + * type: string + * description: 消息类型 + * datetime: + * type: number + * description: 时间戳 + * 400: + * description: 请求参数错误 + */ +function createConnection(req: Request, res: Response): void { + // 从请求头获取会话ID + const sessionId: string = req.header('session-id'); + // 从请求体获取连接ID + const { connectionId } = req.body; + // 获取会话的最后请求时间 + const datetime = lastRequestedTime.get(sessionId); + + // 检查连接ID是否存在 + if (connectionId == null) { + res.status(400).send({ error: new Error(`connectionId is required`) }); + return; + } + let polite = true; + // 处理私有模式 + if (isPrivate) { + if (connectionPair.has(connectionId)) { + const pair = connectionPair.get(connectionId); + + // 检查连接ID是否已被使用 + if (pair[0] != null && pair[1] != null) { + const err = new Error(`${connectionId}: This connection id is already used.`); + console.log(err); + res.status(400).send({ error: err }); + return; + } else if (pair[0] != null) { + // 找到配对连接 + connectionPair.set(connectionId, [pair[0], sessionId]); + // 添加连接ID到另一个会话 + const map = getOrCreateConnectionIds(pair[0]); + map.add(connectionId); + } + } else { + // 创建新的连接对 + connectionPair.set(connectionId, [sessionId, null]); + polite = false; + } + } + + // 添加连接ID到当前会话 + const connectionIds = getOrCreateConnectionIds(sessionId); + connectionIds.add(connectionId); + // 返回JSON响应,包含连接信息 + res.json({ connectionId: connectionId, polite: polite, type: "connect", datetime: datetime }); +} + +/** + * @swagger + * /signaling/connection: + * delete: + * summary: 删除连接 + * description: 删除指定的连接 + * security: + * - sessionAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * responses: + * 200: + * description: 成功删除连接 + * content: + * application/json: + * schema: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + */ +function deleteConnection(req: Request, res: Response): void { + // 从请求头获取会话ID + const sessionId: string = req.header('session-id'); + // 从请求体获取连接ID + const { connectionId } = req.body; + // 获取会话的最后请求时间 + const datetime = lastRequestedTime.get(sessionId); + + // 删除连接 + _deleteConnection(sessionId, connectionId, datetime); + + // 返回JSON响应,包含连接ID + res.json({ connectionId: connectionId }); +} + +/** + * @swagger + * /signaling/offer: + * post: + * summary: 发送offer信令 + * description: 发送offer信令消息 + * security: + * - sessionAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * sdp: + * type: string + * description: SDP描述 + * responses: + * 200: + * description: 成功发送offer信令 + */ +function postOffer(req: Request, res: Response): void { + const sessionId: string = req.header('session-id'); + const { connectionId } = req.body; + const datetime = lastRequestedTime.get(sessionId); + let keySessionId = null; + let polite = false; + + if (isPrivate) { + if (connectionPair.has(connectionId)) { + const pair = connectionPair.get(connectionId); + keySessionId = pair[0] == sessionId ? pair[1] : pair[0]; + if (keySessionId != null) { + polite = true; + const map = offers.get(keySessionId); + map.set(connectionId, new Offer(req.body.sdp, datetime, polite)); + } + } + res.sendStatus(200); + return; + } + + if(!connectionPair.has(connectionId)) + { + connectionPair.set(connectionId, [sessionId, null]); + } + + keySessionId = sessionId; + const map = offers.get(keySessionId); + map.set(connectionId, new Offer(req.body.sdp, datetime, polite)); + + res.sendStatus(200); +} + +/** + * @swagger + * /signaling/answer: + * post: + * summary: 发送answer信令 + * description: 发送answer信令消息 + * security: + * - sessionAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * sdp: + * type: string + * description: SDP描述 + * responses: + * 200: + * description: 成功发送answer信令 + */ +function postAnswer(req: Request, res: Response): void { + const sessionId: string = req.header('session-id'); + const { connectionId } = req.body; + const datetime = lastRequestedTime.get(sessionId); + const connectionIds = getOrCreateConnectionIds(sessionId); + connectionIds.add(connectionId); + + if (!connectionPair.has(connectionId)) { + res.sendStatus(200); + return; + } + + // add connectionPair + const pair = connectionPair.get(connectionId); + const otherSessionId = pair[0] == sessionId ? pair[1] : pair[0]; + if (!clients.has(otherSessionId)) { + // already deleted + res.sendStatus(200); + return; + } + + if (!isPrivate) { + connectionPair.set(connectionId, [otherSessionId, sessionId]); + } + + const map = answers.get(otherSessionId); + map.set(connectionId, new Answer(req.body.sdp, datetime)); + + // update datetime for candidates + const mapCandidates = candidates.get(otherSessionId); + if (mapCandidates) { + const arrayCandidates = mapCandidates.get(connectionId); + if (arrayCandidates) { + for (const candidate of arrayCandidates) { + candidate.datetime = datetime; + } + } + } + res.sendStatus(200); +} + +/** + * @swagger + * /signaling/candidate: + * post: + * summary: 发送candidate信令 + * description: 发送candidate信令消息 + * security: + * - sessionAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * candidate: + * type: string + * description: ICE候选者信息 + * sdpMLineIndex: + * type: number + * description: SDP媒体行索引 + * sdpMid: + * type: string + * description: SDP媒体ID + * responses: + * 200: + * description: 成功发送candidate信令 + */ +function postCandidate(req: Request, res: Response): void { + const sessionId: string = req.header('session-id'); + const { connectionId } = req.body; + const datetime = lastRequestedTime.get(sessionId); + + const map = candidates.get(sessionId); + if (!map.has(connectionId)) { + map.set(connectionId, []); + } + const arr = map.get(connectionId); + const candidate = new Candidate(req.body.candidate, req.body.sdpMLineIndex, req.body.sdpMid, datetime); + arr.push(candidate); + res.sendStatus(200); +} +/** + * @swagger + * /signaling/rooms: + * get: + * summary: 获取房间和用户信息 + * description: 获取所有房间的信息,包括房间ID和链接的用户 + * security: + * - sessionAuth: [] + * responses: + * 200: + * description: 成功获取房间和用户信息 + * content: + * application/json: + * schema: + * type: object + * properties: + * rooms: + * type: array + * items: + * type: object + * properties: + * roomId: + * type: string + * description: 房间ID + * users: + * type: array + * items: + * type: object + * properties: + * sessionId: + * type: string + * description: 会话ID + * connected: + * type: boolean + * description: 连接状态 + * userCount: + * type: number + * description: 用户数量 + * totalRooms: + * type: number + * description: 总房间数 + */ +function onGetConnections(req: Request, res: Response): void { + + // 收集所有房间ID和链接用户信息 + const rooms = []; + + // 遍历所有连接对 + for (const [connectionId, pair] of Array.from(connectionPair.entries())) { + // 收集房间中的用户信息 + const users = []; + + // 添加第一个用户 + if (pair[0] && clients.has(pair[0])) { + users.push({ + sessionId: pair[0], + connected: true + }); + } + + // 添加第二个用户 + if (pair[1] && clients.has(pair[1])) { + users.push({ + sessionId: pair[1], + connected: true + }); + } + + // 添加房间信息 + rooms.push({ + roomId: connectionId, + users: users, + userCount: users.length + }); + } + + res.json({ rooms: rooms, totalRooms: rooms.length }); +} + +/** + * @swagger + * /signaling/connection-ids: + * get: + * summary: 获取所有连接ID + * description: 获取所有当前活跃的连接ID + * security: + * - sessionAuth: [] + * responses: + * 200: + * description: 成功获取连接ID列表 + * content: + * application/json: + * schema: + * type: object + * properties: + * connectionIds: + * type: array + * items: + * type: string + * description: 连接ID + * totalCount: + * type: number + * description: 总连接数 + */ +function getAllConnectionIds(req: Request, res: Response): void { + // 获取所有连接ID + const connectionIds = onGetAllConnectionIds(); + // 返回JSON响应,包含连接ID列表和总数量 + res.json({ connectionIds: connectionIds, totalCount: connectionIds.length }); +} +/** + * 导出HTTP处理器函数 + */ +export { + reset, // 重置处理器状态 + checkSessionId, // 检查会话ID是否有效 + getAll, // 获取所有信令消息 + getConnection, // 获取连接列表 + getOffer, // 获取offer列表 + getAnswer, // 获取answer列表 + getCandidate, // 获取candidate列表 + createSession, // 创建会话 + deleteSession, // 删除会话 + createConnection, // 创建连接 + deleteConnection, // 删除连接 + postOffer, // 处理offer信令消息 + postAnswer, // 处理answer信令消息 + postCandidate, // 处理candidate信令消息 + onGetConnections, // 获取房间和用户信息 + getAllConnectionIds // 获取所有连接ID +}; diff --git a/src/class/offer.ts b/src/class/offer.ts new file mode 100644 index 0000000..701ed6e --- /dev/null +++ b/src/class/offer.ts @@ -0,0 +1,10 @@ +export default class Offer { + sdp: string; + datetime: number; + polite: boolean; + constructor(sdp: string, datetime: number, polite: boolean) { + this.sdp = sdp; + this.datetime = datetime; + this.polite = polite; + } +} diff --git a/src/class/options.ts b/src/class/options.ts new file mode 100644 index 0000000..6648366 --- /dev/null +++ b/src/class/options.ts @@ -0,0 +1,9 @@ +export default interface Options { + secure?: boolean; + port?: number; + keyfile?: string; + certfile?: string; + type?: string; + mode?: string; + logging?: string; +} diff --git a/src/class/websockethandler.ts b/src/class/websockethandler.ts new file mode 100644 index 0000000..675fb1d --- /dev/null +++ b/src/class/websockethandler.ts @@ -0,0 +1,475 @@ +/** + * WebSocket处理器 + * 负责管理WebSocket连接和信令消息处理 + */ +import Offer from './offer'; +import Answer from './answer'; +import Candidate from './candidate'; + +/** + * 是否为私有模式 + */ +let isPrivate: boolean; + +/** + * 客户端连接映射 + * 键: WebSocket实例 + * 值: 该WebSocket的连接ID集合 + */ +const clients: Map> = new Map>(); + +/** + * 连接组结构 + * host: 主机WebSocket实例(第一个连接的客户端) + * participants: 参与者WebSocket集合(后续连接的客户端) + */ +interface ConnectionGroup { + host: WebSocket; + participants: Set; +} + +/** + * 连接组映射 + * 键: connectionId + * 值: ConnectionGroup(1个host + 多个participants) + */ +const connectionGroup: Map = new Map(); + +/** + * 获取或创建WebSocket会话的连接ID集合 + * @param session WebSocket会话实例 + * @returns 连接ID的Set集合 + */ +function getOrCreateConnectionIds(session: WebSocket): Set { + let connectionIds = null; + // 检查客户端是否已存在 + if (!clients.has(session)) { + // 如果不存在,创建新的连接ID集合 + connectionIds = new Set(); + // 将新的连接ID集合与客户端关联 + clients.set(session, connectionIds); + } + // 获取客户端的连接ID集合 + connectionIds = clients.get(session); + // 返回连接ID集合 + return connectionIds; +} + +/** + * 重置处理器状态 + * @param mode 通信模式(public或private) + */ +function reset(mode: string): void { + // 设置是否为私有模式 + isPrivate = mode == "private"; +} + +/** + * 添加新的WebSocket连接 + * @param ws WebSocket连接实例 + */ +function add(ws: WebSocket): void { + // 为新连接创建空的连接ID集合 + const id = new Set(); + clients.set(ws, id); + // 记录添加WebSocket连接的日志 + console.log(`Add WebSocket: ${ws.url}`); +} + +/** + * 判断WebSocket是否为指定连接组的host + * @param ws WebSocket连接实例 + * @param connectionId 连接ID + * @returns 是否为host + */ +function isHost(ws: WebSocket, connectionId: string): boolean { + const group = connectionGroup.get(connectionId); + return group != null && group.host === ws; +} + +/** + * 向连接组中除发送者外的所有成员发送消息 + * @param connectionId 连接ID + * @param senderWs 发送者WebSocket实例 + * @param message 要发送的消息对象 + */ +function broadcastToGroup(connectionId: string, senderWs: WebSocket, message: any): void { + const group = connectionGroup.get(connectionId); + if (!group) return; + // 如果发送者是host,转发给所有participants + if (senderWs === group.host) { + group.participants.forEach(participantWs => { + participantWs.send(JSON.stringify(message)); + }); + } else { + // 如果发送者是participant,转发给host + group.host.send(JSON.stringify(message)); + } +} + +/** + * 移除WebSocket连接 + * @param ws WebSocket连接实例 + */ +function remove(ws: WebSocket): void { + const connectionIds = clients.get(ws); + if (!connectionIds) return; + + connectionIds.forEach(connectionId => { + const group = connectionGroup.get(connectionId); + if (group) { + if (group.host === ws) { + group.participants.forEach(participantWs => { + participantWs.send(JSON.stringify({ type: "disconnect", connectionId: connectionId, reason: "host-left" })); + }); + connectionGroup.delete(connectionId); + } else { + group.participants.delete(ws); + // 包含participantId,让host能识别是哪个participant离开 + group.host.send(JSON.stringify({ type: "participant-left", connectionId: connectionId, participantId: (ws as any).participantId })); + } + } + console.log(`Remove connectionId: ${connectionId}`); + }); + + clients.delete(ws); +} + +/** + * 处理连接请求(1对多模式) + * 第一个连接的客户端成为host,后续连接的客户端成为participants + * @param ws WebSocket连接实例 + * @param connectionId 连接ID + */ +function onConnect(ws: WebSocket, connectionId: string): void { + let polite = true; + // 为每个WebSocket生成唯一的participantId + const participantId = (ws as any).participantId = (ws as any).participantId || `p_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; + + if (isPrivate) { + if (connectionGroup.has(connectionId)) { + const group = connectionGroup.get(connectionId); + group.participants.add(ws); + console.log(`Participant ${participantId} joined connectionId: ${connectionId}, total participants: ${group.participants.size}`); + // 通知host有新participant加入 + group.host.send(JSON.stringify({ type: "participant-joined", connectionId: connectionId, participantId: participantId })); + } else { + connectionGroup.set(connectionId, { host: ws, participants: new Set() }); + polite = false; + console.log(`Host created connectionId: ${connectionId}`); + } + } + + const connectionIds = getOrCreateConnectionIds(ws); + connectionIds.add(connectionId); + const role = polite ? 'participant' : 'host'; + ws.send(JSON.stringify({ type: "connect", connectionId: connectionId, polite: polite, role: role, participantId: participantId })); +} + +/** + * 处理断开连接请求(1对多模式) + * @param ws WebSocket连接实例 + * @param connectionId 连接ID + */ +function onDisconnect(ws: WebSocket, connectionId: string): void { + // 获取连接的连接ID集合 + const connectionIds = clients.get(ws); + if (connectionIds) { + // 从集合中删除连接ID + connectionIds.delete(connectionId); + } + + // 处理连接组 + const group = connectionGroup.get(connectionId); + if (group) { + if (group.host === ws) { + // host断开连接,通知所有participants房间已关闭,并删除连接组 + group.participants.forEach(participantWs => { + participantWs.send(JSON.stringify({ type: "disconnect", connectionId: connectionId, reason: "host-left" })); + }); + connectionGroup.delete(connectionId); + console.log(`Host disconnected, room ${connectionId} deleted, notified ${group.participants.size} participants`); + } else { + // participant断开连接,从组中移除并通知host(使用participant-left类型,host不会关闭房间) + group.participants.delete(ws); + group.host.send(JSON.stringify({ type: "participant-left", connectionId: connectionId, participantId: (ws as any).participantId })); + console.log(`Participant left connectionId: ${connectionId}, remaining participants: ${group.participants.size}`); + } + } + + // 向当前连接发送断开连接消息 + ws.send(JSON.stringify({ type: "disconnect", connectionId: connectionId })); + //RemoveHeartbeat(ws); + // 记录断开连接的日志 + console.log(`Disconnect connectionId: ${connectionId}`); +} + +/** + * 处理offer信令(1对多模式) + * host的offer转发给所有participants,participant的offer转发给host + * @param ws WebSocket连接实例 + * @param message 消息数据 + */ +function onOffer(ws: WebSocket, message: any): void { + const connectionId = message.connectionId as string; + const newOffer = new Offer(message.sdp, Date.now(), false); + + if (isPrivate) { + if (connectionGroup.has(connectionId)) { + const group = connectionGroup.get(connectionId); + const senderParticipantId = (ws as any).participantId; + const targetParticipantId = message.participantId; + if (group.host === ws) { + // host发送offer给特定participant(多peer模式下按participantId路由) + newOffer.polite = true; + if (targetParticipantId) { + // 路由到指定participant + group.participants.forEach(participantWs => { + if ((participantWs as any).participantId === targetParticipantId) { + participantWs.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer, participantId: targetParticipantId })); + } + }); + } else { + // 兼容:无目标时广播给所有participants + group.participants.forEach(participantWs => { + const pid = (participantWs as any).participantId; + participantWs.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer, participantId: pid })); + }); + } + } else { + // participant发送offer给host,携带该participant的participantId + newOffer.polite = true; + group.host.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer, participantId: senderParticipantId })); + } + } + return; + } + + // 公共模式:创建新的连接组(如果不存在) + if (!connectionGroup.has(connectionId)) { + connectionGroup.set(connectionId, { host: ws, participants: new Set() }); + } + // 向所有其他客户端广播offer + clients.forEach((_v, k) => { + if (k == ws) { + return; + } + k.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer })); + }); +} + +/** + * 处理answer信令(1对多模式) + * participant的answer转发给host + * @param ws WebSocket连接实例 + * @param message 消息数据 + */ +function onAnswer(ws: WebSocket, message: any): void { + const connectionId = message.connectionId as string; + const connectionIds = getOrCreateConnectionIds(ws); + connectionIds.add(connectionId); + const newAnswer = new Answer(message.sdp, Date.now()); + + if (!connectionGroup.has(connectionId)) { + return; + } + + const group = connectionGroup.get(connectionId); + const senderParticipantId = (ws as any).participantId; + // 从answer消息中获取目标participantId(host回复时指定) + const targetParticipantId = message.participantId; + + if (group.host === ws) { + // host发送answer给特定participant + if (targetParticipantId) { + group.participants.forEach(participantWs => { + if ((participantWs as any).participantId === targetParticipantId) { + participantWs.send(JSON.stringify({ from: connectionId, to: "", type: "answer", data: newAnswer, participantId: targetParticipantId })); + } + }); + } else { + // 兼容:没有targetParticipantId时广播给所有participants + group.participants.forEach(participantWs => { + participantWs.send(JSON.stringify({ from: connectionId, to: "", type: "answer", data: newAnswer })); + }); + } + } else { + // participant发送answer给host,携带自己的participantId + group.host.send(JSON.stringify({ from: connectionId, to: "", type: "answer", data: newAnswer, participantId: senderParticipantId })); + } +} + +/** + * 处理candidate信令(1对多模式) + * host的candidate转发给所有participants,participant的candidate转发给host + * @param ws WebSocket连接实例 + * @param message 消息数据 + */ +function onCandidate(ws: WebSocket, message: any): void { + const connectionId = message.connectionId; + const candidate = new Candidate(message.candidate, message.sdpMLineIndex, message.sdpMid, Date.now()); + const senderParticipantId = (ws as any).participantId; + const targetParticipantId = message.participantId; + + if (isPrivate) { + if (connectionGroup.has(connectionId)) { + const group = connectionGroup.get(connectionId); + if (group.host === ws) { + // host发送candidate给特定participant + if (targetParticipantId) { + group.participants.forEach(participantWs => { + if ((participantWs as any).participantId === targetParticipantId) { + participantWs.send(JSON.stringify({ from: connectionId, to: "", type: "candidate", data: candidate, participantId: targetParticipantId })); + } + }); + } else { + group.participants.forEach(participantWs => { + participantWs.send(JSON.stringify({ from: connectionId, to: "", type: "candidate", data: candidate })); + }); + } + } else { + // participant发送candidate给host,携带自己的participantId + group.host.send(JSON.stringify({ from: connectionId, to: "", type: "candidate", data: candidate, participantId: senderParticipantId })); + } + } + return; + } +} + +function onCallConnectionId(ws: WebSocket, message: any): void { + // 获取连接ID + const connectionId = message.connectionId; + const clientId = message.clientId; + // 在1对多模式下,通知host有新的呼叫请求 + if (connectionGroup.has(connectionId)) { + const group = connectionGroup.get(connectionId); + if (group.host !== ws) { + // participant发起呼叫,通知host + group.host.send(JSON.stringify({ from: connectionId, to: "", type: "call-request", data: connectionId })); + } + } else { + // 兼容旧的广播方式 + clients.forEach((_v, k) => { + if (k === ws) { + return; + } + if (_v == clientId) { + k.send(JSON.stringify({ from: connectionId, to: "", type: "call-request", data: connectionId })); + } + }); + } +} + + +/** + * 处理广播消息请求(1对多模式) + * @param ws WebSocket连接实例 + * @param message 消息数据 + */ +function onBroadcast(ws: WebSocket, message: any): void { + const broadcastMessage = message.message; + const targetConnectionId = message.targetConnectionId; + + if (targetConnectionId) { + // 向指定连接组广播 + if (connectionGroup.has(targetConnectionId)) { + const group = connectionGroup.get(targetConnectionId); + // 向组内所有成员发送消息 + group.host.send(JSON.stringify({ + type: "broadcast", + message: broadcastMessage, + from: "server" + })); + group.participants.forEach(participantWs => { + participantWs.send(JSON.stringify({ + type: "broadcast", + message: broadcastMessage, + from: "server" + })); + }); + } + } else { + // 全局广播:向所有客户端发送消息 + clients.forEach((_v, k) => { + k.send(JSON.stringify({ + type: "broadcast", + message: broadcastMessage, + from: "server" + })); + }); + } +} + +function AddHeartbeat(ws: WebSocket, connectionId: string) { + // 初始化心跳检测 + (ws as any).lastActivity = Date.now(); + + // 设置心跳检测定时器,每30秒发送一次ping + (ws as any).heartbeatTimer = setInterval(() => { + const now = Date.now(); + // 检查上次活动时间,如果超过60秒没有活动,关闭连接 + if (now - (ws as any).lastActivity > 10000) { + console.log('WebSocket connection timeout, closing...'); + clearInterval((ws as any).heartbeatTimer); + //ws.close(); + onDisconnect(ws, connectionId); + } else { + // 发送ping消息 + ws.send(JSON.stringify({ from: connectionId, to: "", type: "on-message", data: { type: "ping"} })); + console.log('WebSocket connection heartbeat, lastActivity: ', (ws as any).lastActivity); + } + }, 3000); +} + +function RemoveHeartbeat(ws: WebSocket) { + // 清除心跳检测定时器 + if ((ws as any).heartbeatTimer) { + clearInterval((ws as any).heartbeatTimer); + } +} + +/** + * 处理获取所有连接ID的请求 + * @param ws WebSocket连接实例 + */ +function onGetAllConnectionIds(): string[] { + // 获取所有connectionId + const connectionIds = Array.from(connectionGroup.keys()); + return connectionIds; +} + +/** + * 处理chat-message信令(1对多模式) + * host的消息转发给所有participants,participant的消息转发给host + * @param ws WebSocket连接实例 + * @param message 消息数据 + */ +function onMessage(ws: WebSocket, message: any): void { + // 获取连接ID + const connectionId = message.connectionId; + const chatMessage = message.message; + const senderParticipantId = (ws as any).participantId; + if (connectionGroup.has(connectionId)) { + const group = connectionGroup.get(connectionId); + if (group.host === ws) { + // host发送消息,转发给所有participants + group.participants.forEach(participantWs => { + participantWs.send(JSON.stringify({ from: connectionId, to: "", type: "on-message", data: chatMessage })); + }); + } else { + // participant发送消息,转发给host(附带participantId)和其他participants + group.host.send(JSON.stringify({ from: connectionId, to: "", type: "on-message", data: chatMessage, participantId: senderParticipantId })); + // 同时转发给其他participants(排除发送者自身) + group.participants.forEach(participantWs => { + if (participantWs !== ws) { + participantWs.send(JSON.stringify({ from: connectionId, to: "", type: "on-message", data: chatMessage, participantId: senderParticipantId })); + } + }); + } + } +} + +/** + * 导出WebSocket处理器函数 + */ +export { reset, add, remove, onConnect, onDisconnect, onOffer, onAnswer, onCandidate, onCallConnectionId, onBroadcast, onGetAllConnectionIds, AddHeartbeat, RemoveHeartbeat, onMessage, isHost, broadcastToGroup, connectionGroup }; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..7e4b4cb --- /dev/null +++ b/src/index.ts @@ -0,0 +1,107 @@ +import { Command } from 'commander'; +import * as express from 'express'; +import * as https from 'https'; +import { Server } from 'http'; +import * as fs from 'fs'; +import * as os from 'os'; +import { createServer } from './server'; +import { AddressInfo } from 'net'; +import WSSignaling from './websocket'; +import Options from './class/options'; + +export class RenderStreaming { + public static run(argv: string[]): RenderStreaming { + const program = new Command(); + const readOptions = (): Options => { + // 确保argv是数组 + const args = Array.isArray(argv) ? argv : process.argv; + + program + .usage('[options] ') + .option('-p, --port ', 'Port to start the server on.', process.env.PORT || `80`) + .option('-s, --secure', 'Enable HTTPS (you need server.key and server.cert).', process.env.SECURE || true) + .option('-k, --keyfile ', 'https key file.', process.env.KEYFILE || 'server.key') + .option('-c, --certfile ', 'https cert file.', process.env.CERTFILE || 'server.cert') + .option('-t, --type ', 'Type of signaling protocol, Choose websocket or http.', process.env.TYPE || 'websocket') + .option('-m, --mode ', 'Choose Communication mode public or private.', process.env.MODE || 'public') + .option('-l, --logging ', 'Choose http logging type combined, dev, short, tiny or none.', process.env.LOGGING || 'dev') + .parse(args); + + const option = program.opts(); + return { + port: option.port, + secure: option.secure == undefined ? false : option.secure, + keyfile: option.keyfile, + certfile: option.certfile, + type: option.type == undefined ? 'websocket' : option.type, + mode: option.mode, + logging: option.logging, + }; + }; + const options = readOptions(); + return new RenderStreaming(options); + } + + public app: express.Application; + + public server?: Server; + + public options: Options; + + constructor(options: Options) { + this.options = options; + this.app = createServer(this.options); + if (this.options.secure) { + this.server = https.createServer({ + key: fs.readFileSync(options.keyfile), + cert: fs.readFileSync(options.certfile), + }, this.app).listen(this.options.port, () => { + const { port } = this.server.address() as AddressInfo; + const addresses = this.getIPAddress(); + for (const address of addresses) { + console.log(`https://${address}:${port}`); + } + }); + } else { + this.server = this.app.listen(this.options.port, () => { + const { port } = this.server.address() as AddressInfo; + const addresses = this.getIPAddress(); + for (const address of addresses) { + console.log(`http://${address}:${port}`); + } + }); + } + if (this.options.type == 'http') { + console.log(`Use http polling for signaling server.`); + } + else if(this.options.type != 'websocket') { + console.log(`signaling type should be set "websocket" or "http". ${this.options.type} is not supported.`); + console.log(`Changing signaling type to websocket.`); + this.options.type = 'websocket'; + } + if (this.options.type == 'websocket') { + console.log(`Use websocket for signaling server ws://${this.getIPAddress()[0]}`); + + //Start Websocket Signaling server + new WSSignaling(this.server, this.options.mode); + } + + console.log(`start as ${this.options.mode} mode`); + } + + getIPAddress(): string[] { + const interfaces = os.networkInterfaces(); + const addresses: string[] = []; + for (const k in interfaces) { + for (const k2 in interfaces[k]) { + const address = interfaces[k][k2]; + if (address.family === 'IPv4') { + addresses.push(address.address); + } + } + } + return addresses; + } +} + +RenderStreaming.run(process.argv); diff --git a/src/log.ts b/src/log.ts new file mode 100644 index 0000000..6268c02 --- /dev/null +++ b/src/log.ts @@ -0,0 +1,27 @@ +const isDebug = true; + +export enum LogLevel { + info, + log, + warn, + error, +} + +export function log(level: LogLevel, ...args: any[]): void { + if (isDebug) { + switch (level) { + case LogLevel.log: + console.log(...args); + break; + case LogLevel.info: + console.info(...args); + break; + case LogLevel.warn: + console.warn(...args); + break; + case LogLevel.error: + console.error(...args); + break; + } + } +} diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..e2d6171 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,89 @@ +import * as express from 'express'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as morgan from 'morgan'; +import signaling from './signaling'; +import { log, LogLevel } from './log'; +import Options from './class/options'; +import { reset as resetHandler }from './class/httphandler'; +import { initSwagger } from './swagger'; + +const cors = require('cors'); +const multer = require('multer'); + +export const createServer = (config: Options): express.Express => { + const app: express.Express = express(); + resetHandler(config.mode); + // logging http access + if (config.logging != "none") { + app.use(morgan(config.logging)); + } + // const signal = require('./signaling'); + app.use(cors({origin: '*'})); + app.use(express.urlencoded({ extended: true })); + app.use(express.json()); + app.get('/config', (req, res) => res.json({ useWebSocket: config.type == 'websocket', startupMode: config.mode, logging: config.logging })); + app.use('/signaling', signaling); + app.use(express.static(path.join(__dirname, '../client/public'))); + app.use('/module', express.static(path.join(__dirname, '../client/src'))); + app.get('/', (req, res) => { + const indexPagePath: string = path.join(__dirname, '../client/public/index.html'); + fs.access(indexPagePath, (err) => { + if (err) { + log(LogLevel.warn, `Can't find file ' ${indexPagePath}`); + res.status(404).send(`Can't find file ${indexPagePath}`); + } else { + res.sendFile(indexPagePath); + } + }); + }); + // 初始化Swagger + initSwagger(app, config); + + // 配置multer存储 + const storage = multer.diskStorage({ + destination: function (req: any, file: any, cb: (error: Error | null, destination: string) => void) { + // 确保上传目录存在 + const uploadDir = path.join(__dirname, '../client/public/uploads/avatars'); + if (!fs.existsSync(uploadDir)) { + fs.mkdirSync(uploadDir, { recursive: true }); + } + cb(null, uploadDir); + }, + filename: function (req: any, file: any, cb: (error: Error | null, filename: string) => void) { + // 临时使用原始文件名,稍后在API处理中重命名 + cb(null, file.originalname); + } + }); + + const upload = multer({ storage: storage }); + + // 头像上传API + app.post('/api/upload/avatar', upload.single('avatar'), (req: any, res: express.Response) => { + if (!req.file) { + return res.status(400).json({ success: false, message: 'No file uploaded' }); + } + + const userId = req.body.userId || 'unknown'; + const ext = path.extname(req.file.originalname); + const oldPath = req.file.path; + const newFilename = `${userId}${ext}`; + const newPath = path.join(path.dirname(oldPath), newFilename); + + // 重命名文件 + fs.rename(oldPath, newPath, (err) => { + if (err) { + console.error('Error renaming file:', err); + return res.status(500).json({ success: false, message: '文件重命名失败' }); + } + + const avatarUrl = `/uploads/avatars/${newFilename}`; + res.json({ success: true, avatarUrl: avatarUrl }); + }); + }); + + // 确保uploads目录可访问 + app.use('/uploads', express.static(path.join(__dirname, '../client/public/uploads'))); + + return app; +}; diff --git a/src/signaling.ts b/src/signaling.ts new file mode 100644 index 0000000..c523e82 --- /dev/null +++ b/src/signaling.ts @@ -0,0 +1,24 @@ +import * as express from 'express'; +import * as handler from'./class/httphandler'; + +const router: express.Router = express.Router(); + +// 不需要会话ID的路由 +router.get('/connection-ids', handler.getAllConnectionIds); + +// 需要会话ID的路由 +router.use(handler.checkSessionId); +router.get('/connection', handler.getConnection); +router.get('/offer', handler.getOffer); +router.get('/answer', handler.getAnswer); +router.get('/candidate', handler.getCandidate); +router.get('', handler.getAll); +router.put('', handler.createSession); +router.delete('', handler.deleteSession); +router.put('/connection', handler.createConnection); +router.delete('/connection', handler.deleteConnection); +router.post('/offer', handler.postOffer); +router.post('/answer', handler.postAnswer); +router.post('/candidate', handler.postCandidate); + +export default router; diff --git a/src/swagger.ts b/src/swagger.ts new file mode 100644 index 0000000..0f2e16e --- /dev/null +++ b/src/swagger.ts @@ -0,0 +1,64 @@ +/** + * Swagger配置文件 + * 用于设置API文档的基本信息和路由 + */ +import * as swaggerJSDoc from 'swagger-jsdoc'; +import * as swaggerUi from 'swagger-ui-express'; +import { Express } from 'express'; +import Options from './class/options'; + +/** + * 初始化Swagger + * @param app Express应用实例 + * @param config 配置选项 + */ +export const initSwagger = (app: Express, config: Options): void => { + // 根据配置生成服务器URL + const protocol = config.secure ? 'https' : 'http'; + const port = config.port || 8080; + const serverUrl = `${protocol}://localhost:${port}`; + + /** + * Swagger配置选项 + */ + const swaggerOptions = { + definition: { + openapi: '3.0.0', + info: { + title: 'WebRTC Signaling API', + version: '1.0.0', + description: 'WebRTC信令服务器API文档', + contact: { + name: 'WebRTC Team', + email: 'contact@webrtc.example.com' + } + }, + servers: [ + { + url: serverUrl, + description: '本地开发服务器' + } + ], + components: { + securitySchemes: { + sessionAuth: { + type: 'apiKey', + in: 'header', + name: 'session-id', + description: '会话ID' + } + } + }, + security: [ + { + sessionAuth: [] + } + ] + }, + apis: ['./src/class/httphandler.ts', './src/signaling.ts'] + }; + + const swaggerSpec = swaggerJSDoc(swaggerOptions); + app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); + console.log(`Swagger文档已初始化,访问 ${serverUrl}/api-docs 查看`); +}; diff --git a/src/websocket.ts b/src/websocket.ts new file mode 100644 index 0000000..f92285c --- /dev/null +++ b/src/websocket.ts @@ -0,0 +1,116 @@ +import * as websocket from "ws"; +import { Server } from 'http'; +import * as handler from "./class/websockethandler"; + +export default class WSSignaling { + server: Server; + wss: websocket.Server; + + /** + * 构造函数,初始化WebSocket信令服务器 + * @param server HTTP服务器实例 + * @param mode 通信模式(public或private) + */ + constructor(server: Server, mode: string) { + // 保存服务器实例 + this.server = server; + // 创建WebSocket服务器 + this.wss = new websocket.Server({ server }); + // 重置处理器,设置通信模式 + handler.reset(mode); + + /** + * 监听WebSocket连接事件 + * @param ws WebSocket连接实例 + */ + this.wss.on('connection', (ws: WebSocket) => { + // 添加新的WebSocket连接到处理器 + handler.add(ws); + //handler.AddHeartbeat(ws); + /** + * 监听连接关闭事件 + */ + ws.onclose = (): void => { + // 从处理器中移除关闭的连接 + handler.remove(ws); + //handler.RemoveHeartbeat(ws); + }; + + /** + * 监听消息事件 + * @param event 消息事件对象 + */ + ws.onmessage = (event: MessageEvent): void => { + // 消息类型说明: + // 1. connect, disconnect 消息格式: + // { type: "connect", connectionId: "连接ID" } + // { type: "disconnect", connectionId: "连接ID" } + // 2. offer, answer, candidate 消息格式: + // { + // type: "offer", + // data: { + // from: "发送方连接ID", + // to: "接收方连接ID", + // data: "信令数据" + // } + // } + // 3. broadcast 消息格式: + // { + // type: "broadcast", + // message: "广播消息内容", + // targetConnectionId: "目标连接ID(可选)" + // } + + // 解析消息数据 + const msg = JSON.parse(event.data); + // 检查消息是否有效 + if (!msg || !this) { + return; + } + + // 打印接收到的消息 + console.log(msg); + + // 根据消息类型处理 + switch (msg.type) { + case "connect": + handler.onConnect(ws, msg.connectionId); + break; + case "disconnect": + handler.onDisconnect(ws, msg.connectionId); + break; + case "offer": + if (msg.participantId !== undefined) msg.data.participantId = msg.participantId; + handler.onOffer(ws, msg.data); + break; + case "answer": + if (msg.participantId !== undefined) msg.data.participantId = msg.participantId; + handler.onAnswer(ws, msg.data); + break; + case "candidate": + if (msg.participantId !== undefined) msg.data.participantId = msg.participantId; + handler.onCandidate(ws, msg.data); + break; + case "ping": + ws.send(JSON.stringify({ type: "pong" })); + break; + case "pong": + (ws as any).lastActivity = Date.now(); + break; + case "broadcast": + handler.onBroadcast(ws, msg.data); + break; + case 'call-request': + handler.onCallConnectionId(ws, msg.data); + break; + case 'on-message': + if (msg.from) msg.data.connectionId = msg.from; + handler.onMessage(ws, msg.data); + break; + default: + break; + } + }; + }); + } +} diff --git a/src/服务端接口与WebSocket消息类型.md b/src/服务端接口与WebSocket消息类型.md new file mode 100644 index 0000000..a25bc91 --- /dev/null +++ b/src/服务端接口与WebSocket消息类型.md @@ -0,0 +1,561 @@ +# 服务端接口与 WebSocket 消息类型 + +## 一、HTTP REST API 接口 + +### 1.1 配置接口 + +**GET /config** +- **功能**: 获取服务器配置信息 +- **参数**: 无 +- **响应**: + ```json + { + "useWebSocket": boolean, + "startupMode": string, + "logging": string + } + ``` + +### 1.2 头像上传接口 + +**POST /api/upload/avatar** +- **功能**: 上传用户头像 +- **参数**: + - `userId` (body): 用户ID + - `avatar` (file): 头像文件 +- **响应**: + ```json + { + "success": boolean, + "avatarUrl": string, + "message": string + } + ``` + +### 1.3 会话管理接口 + +**GET /signaling/connection-ids** +- **功能**: 获取所有活跃的连接ID(无需会话认证) +- **响应**: + ```json + { + "connectionIds": string[], + "totalCount": number + } + ``` + +**PUT /signaling** +- **功能**: 创建新的会话,获取会话ID +- **参数**: 无 +- **响应**: + ```json + { + "sessionId": string + } + ``` + +**GET /signaling** +- **功能**: 获取当前会话的所有信令消息 +- **认证**: 需要在请求头 `Session-Id` 中提供会话ID +- **参数**: + - `fromtime` (query): 起始时间戳,用于增量拉取 +- **响应**: + ```json + { + "messages": [ + { + "connectionId": string, + "type": "connect|disconnect|offer|answer|candidate", + "datetime": number, + "sdp": string, + "polite": boolean, + "candidate": string, + "sdpMLineIndex": number, + "sdpMid": string + } + ], + "datetime": number + } + ``` + +**DELETE /signaling** +- **功能**: 删除当前会话及其所有连接 +- **认证**: 需要会话ID +- **响应**: 200 OK + +### 1.4 连接管理接口 + +**GET /signaling/connection** +- **功能**: 获取当前会话的连接列表 +- **认证**: 需要会话ID +- **响应**: + ```json + { + "connections": [ + { + "connectionId": string, + "type": "connect", + "datetime": number + } + ] + } + ``` + +**PUT /signaling/connection** +- **功能**: 创建新的连接 +- **认证**: 需要会话ID +- **请求体**: + ```json + { + "connectionId": string + } + ``` +- **响应**: + ```json + { + "connectionId": string, + "polite": boolean, + "type": "connect", + "datetime": number + } + ``` + +**DELETE /signaling/connection** +- **功能**: 删除指定的连接 +- **认证**: 需要会话ID +- **请求体**: + ```json + { + "connectionId": string + } + ``` +- **响应**: + ```json + { + "connectionId": string + } + ``` + +### 1.5 WebRTC 信令交换接口 + +**GET /signaling/offer** +- **功能**: 获取 offer 信令消息列表 +- **认证**: 需要会话ID +- **参数**: `fromtime` (query): 起始时间戳 +- **响应**: + ```json + { + "offers": [ + { + "connectionId": string, + "sdp": string, + "polite": boolean, + "type": "offer", + "datetime": number + } + ] + } + ``` + +**POST /signaling/offer** +- **功能**: 发送 offer 信令 +- **认证**: 需要会话ID +- **请求体**: + ```json + { + "connectionId": string, + "sdp": string + } + ``` +- **响应**: 200 OK + +**GET /signaling/answer** +- **功能**: 获取 answer 信令消息列表 +- **认证**: 需要会话ID +- **参数**: `fromtime` (query): 起始时间戳 +- **响应**: + ```json + { + "answers": [ + { + "connectionId": string, + "sdp": string, + "type": "answer", + "datetime": number + } + ] + } + ``` + +**POST /signaling/answer** +- **功能**: 发送 answer 信令 +- **认证**: 需要会话ID +- **请求体**: + ```json + { + "connectionId": string, + "sdp": string + } + ``` +- **响应**: 200 OK + +**GET /signaling/candidate** +- **功能**: 获取 ICE candidate 信令消息列表 +- **认证**: 需要会话ID +- **参数**: `fromtime` (query): 起始时间戳 +- **响应**: + ```json + { + "candidates": [ + { + "connectionId": string, + "candidate": string, + "sdpMLineIndex": number, + "sdpMid": string, + "type": "candidate", + "datetime": number + } + ] + } + ``` + +**POST /signaling/candidate** +- **功能**: 发送 ICE candidate 信令 +- **认证**: 需要会话ID +- **请求体**: + ```json + { + "connectionId": string, + "candidate": string, + "sdpMLineIndex": number, + "sdpMid": string + } + ``` +- **响应**: 200 OK + +### 1.6 房间信息接口 + +**GET /signaling/rooms** +- **功能**: 获取房间和用户信息 +- **认证**: 需要会话ID +- **响应**: + ```json + { + "rooms": [ + { + "roomId": string, + "users": [ + { + "sessionId": string, + "connected": boolean + } + ], + "userCount": number + } + ], + "totalRooms": number + } + ``` + +--- + +## 二、WebSocket 消息类型 + +### 2.1 连接生命周期消息 + +#### connect(连接建立) + +- **方向**: 客户端 → 服务端 → 客户端 +- **客户端发送**: + ```json + { + "type": "connect", + "connectionId": string + } + ``` +- **服务端响应**: + ```json + { + "type": "connect", + "connectionId": string, + "polite": boolean, + "role": "host|participant", + "participantId": string + } + ``` +- **说明**: 建立连接并协商 polite 标志以处理连接冲突。`polite=true` 表示后加入方(participant),`polite=false` 表示先加入方(host)。 + +#### disconnect(连接断开) + +- **方向**: 客户端 → 服务端 → 客户端 +- **客户端发送**: + ```json + { + "type": "disconnect", + "connectionId": string + } + ``` +- **服务端响应**: + ```json + { + "type": "disconnect", + "connectionId": string, + "reason": "normal|host-left" + } + ``` +- **说明**: 断开连接。当 host 离开时,reason 为 `host-left`。 + +#### participant-joined(参与者加入,仅私有模式) + +- **方向**: 服务端 → host 客户端 +- **格式**: + ```json + { + "type": "participant-joined", + "connectionId": string, + "participantId": string + } + ``` +- **说明**: 通知 host 有新的 participant 加入。 + +#### participant-left(参与者离开,仅私有模式) + +- **方向**: 服务端 → host 客户端 / 其他 participants +- **格式**: + ```json + { + "type": "participant-left", + "connectionId": string, + "participantId": string + } + ``` +- **说明**: 通知有 participant 离开房间。 + +### 2.2 WebRTC SDP 交换消息 + +#### offer + +- **方向**: 双向 +- **格式**: + ```json + { + "type": "offer", + "from": string, + "to": string, + "data": { + "sdp": string, + "connectionId": string, + "participantId": string + }, + "participantId": string + } + ``` +- **路由规则**: + - **私有模式**: Host → 所有/特定 Participant;Participant → Host + - **公共模式**: Peer → 所有其他 Peers + +#### answer + +- **方向**: 双向 +- **格式**: + ```json + { + "type": "answer", + "from": string, + "to": string, + "data": { + "sdp": string, + "connectionId": string, + "participantId": string + }, + "participantId": string + } + ``` +- **路由规则**: + - **私有模式**: Participant → Host;Host → 特定 Participant + - **公共模式**: Peer → 特定 Peer + +#### candidate + +- **方向**: 双向 +- **格式**: + ```json + { + "type": "candidate", + "from": string, + "to": string, + "data": { + "candidate": string, + "sdpMLineIndex": number, + "sdpMid": string, + "connectionId": string, + "participantId": string + }, + "participantId": string + } + ``` +- **路由规则**: 与 answer 消息相同。 + +### 2.3 心跳/控制消息 + +#### ping(服务端 → 客户端) + +- **格式**: + ```json + { + "from": string, + "to": string, + "type": "on-message", + "data": { + "type": "ping" + } + } + ``` +- **说明**: 服务端心跳检测(可选功能,默认未启用)。 + +#### pong(客户端 → 服务端) + +- **格式**: + ```json + { + "type": "pong" + } + ``` +- **说明**: 心跳应答。 + +### 2.4 自定义消息 + +#### on-message(通用消息传递) + +- **方向**: 双向 +- **格式**: + ```json + { + "type": "on-message", + "from": string, + "to": string, + "data": { + "message": string|object, + "connectionId": string, + "senderId": string, + "participantId": string + } + } + ``` +- **路由规则**: + - **私有模式**: Host ↔ 所有 Participants;Participant ↔ Host + - **公共模式**: Peer → Peer +- **说明**: 传输文本、数据或聊天消息。 + +#### broadcast(广播消息) + +- **客户端发送**: + ```json + { + "type": "broadcast", + "message": string|object, + "targetConnectionId": string + } + ``` +- **服务端转发**: + ```json + { + "type": "broadcast", + "message": string|object, + "from": "server" + } + ``` +- **说明**: 若指定 `targetConnectionId`,则广播给该连接组内的所有成员;否则广播给所有连接的客户端。 + +### 2.5 呼叫请求消息 + +#### call-request + +- **方向**: 客户端 → 服务端 → 客户端 +- **格式**: + ```json + { + "type": "call-request", + "data": string + } + ``` +- **路由规则**: + - **私有模式**: Participant → Server → Host + - **公共模式**: Peer → Server → 所有其他 Peers +- **说明**: 发起呼叫请求。 + +--- + +## 三、通信模式说明 + +### 3.1 公共模式(Public Mode) + +- 所有连接的客户端都可以相互通信 +- offer/answer/candidate 向所有其他客户端广播 + +### 3.2 私有模式(Private Mode) + +- 一个 connectionId 对应一个房间,包含 1 个 host 和多个 participants +- Host 是第一个加入 connectionId 的客户端(`polite=false`) +- Participants 是后续加入的客户端(`polite=true`) +- Host 发送的消息可单播给特定 participant 或广播给所有 participants +- Participant 发送的消息仅发送给 host +- 当 host 离开时,整个房间关闭,所有 participants 被断开连接 +- 当 participant 离开时,房间继续存在 + +--- + +## 四、会话与连接管理 + +### 4.1 会话(Session) + +- 每个客户端通过 `PUT /signaling` 创建一个唯一的会话 +- 会话ID通过 `Session-Id` 请求头在所有后续 HTTP 请求中传递 +- 会话管理超时:10 秒无请求则自动删除会话 +- 一个会话可以包含多个连接ID + +### 4.2 连接(Connection) + +- 连接ID由客户端生成和指定 +- 在 HTTP 模式下,连接对通过 connectionId 自动配对 +- 在 WebSocket 私有模式下,第一个连接是 host,后续连接是 participants +- 一个连接对在私有模式下支持 1 对多(1 host + N participants) + +### 4.3 Polite 标志 + +- 用于处理 WebRTC 连接冲突(双方同时发送 offer 的情况) +- `polite=true`:该端应放弃 offer,接收来自另一端的 offer +- `polite=false`:该端具有优先权,可以发送 offer +- 私有模式下:host → `polite=false`,participants → `polite=true` + +--- + +## 五、关键设计特性 + +1. **双协议支持**: 同时支持 HTTP 轮询和 WebSocket 两种信令协议 +2. **两种通信模式**: 公共模式(全连通)和私有模式(1 对多房间) +3. **Polite 机制**: 自动处理并发 offer 冲突 +4. **会话隔离**: 通过 Session-ID 在 HTTP 模式下隔离不同客户端的消息 +5. **心跳检测**: 可选的心跳机制防止连接超时 +6. **消息增量拉取**: HTTP GET 支持 fromtime 参数实现增量消息获取 +7. **头像上传**: 内置文件上传功能支持用户头像管理 +8. **API 文档**: 集成 Swagger 提供自动生成的 API 文档 + +--- + +## 六、源文件路径 + +| 文件 | 功能 | +|------|------| +| `src/index.ts` | 应用入口,配置和启动服务器 | +| `src/server.ts` | Express 服务器创建,中间件配置 | +| `src/signaling.ts` | HTTP REST 路由定义 | +| `src/websocket.ts` | WebSocket 服务器和消息路由 | +| `src/class/httphandler.ts` | HTTP 处理器,核心业务逻辑 | +| `src/class/websockethandler.ts` | WebSocket 处理器,消息分发逻辑 | +| `src/class/offer.ts` | Offer 类定义 | +| `src/class/answer.ts` | Answer 类定义 | +| `src/class/candidate.ts` | Candidate 类定义 | +| `src/class/options.ts` | 配置选项接口 | +| `src/swagger.ts` | Swagger API 文档配置 | +| `src/log.ts` | 日志工具 | diff --git a/test/env_macos.postman_environment.json b/test/env_macos.postman_environment.json new file mode 100644 index 0000000..8d32ec8 --- /dev/null +++ b/test/env_macos.postman_environment.json @@ -0,0 +1,14 @@ +{ + "id": "0ff4f10b-de1b-4376-bf88-c51b03b4d25b", + "name": "env_macos", + "values": [ + { + "key": "url", + "value": "localhost:8080", + "enabled": true + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2019-07-06T09:29:02.003Z", + "_postman_exported_using": "Postman/7.2.2" +} \ No newline at end of file diff --git a/test/httphandler.test.ts b/test/httphandler.test.ts new file mode 100644 index 0000000..615eb31 --- /dev/null +++ b/test/httphandler.test.ts @@ -0,0 +1,509 @@ +import { getMockReq, getMockRes } from '@jest-mock/express'; +import * as httpHandler from '../src/class/httphandler'; + +const RetriesToForceTimeout = 11; // Waits a second each time, timeout is 10 sec for httphandler. + +describe('http signaling test in public mode', () => { + const sessionId = "abcd1234"; + const sessionId2 = "abcd5678"; + const sessionId3 = "abcd9101112"; + const connectionId = "12345"; + const connectionId2 = "67890"; + const testsdp = "test sdp"; + + const { res, next, mockClear } = getMockRes(); + const req = getMockReq({ header: jest.fn(() => sessionId) }); + const req2 = getMockReq({ header: jest.fn(() => sessionId2) }); + const req3 = getMockReq({ header: jest.fn(() => sessionId3) }); + + beforeAll(() => { + httpHandler.reset("public"); + }); + + beforeEach(() => { + mockClear(); + httpHandler.checkSessionId(req, res, next); + httpHandler.checkSessionId(req2, res, next); + }); + + test('throw check has session', async () => { + httpHandler.checkSessionId(req, res, next); + expect(res.sendStatus).toHaveBeenCalledWith(404); + expect(next).not.toHaveBeenCalled(); + }); + + test('create session', async () => { + await httpHandler.createSession(sessionId, res); + expect(res.json).toHaveBeenCalledWith({ sessionId: sessionId }); + }); + + test('create session2', async () => { + await httpHandler.createSession(sessionId2, res); + expect(res.json).toHaveBeenCalledWith({ sessionId: sessionId2 }); + }); + + test('create connection from session1', async () => { + const body = { connectionId: connectionId }; + req.body = body; + await httpHandler.createConnection(req, res); + expect(res.json).toHaveBeenCalledWith({ connectionId: connectionId, polite: true, datetime: expect.anything(), type: "connect" }); + }); + + test('create connection from session2', async () => { + const body = { connectionId: connectionId2 }; + req2.body = body; + await httpHandler.createConnection(req2, res); + expect(res.json).toHaveBeenCalledWith({ connectionId: connectionId2, polite: true, datetime: expect.anything(), type: "connect" }); + }); + + test('get connection from session1', async () => { + await httpHandler.getConnection(req, res); + const connect = { connectionId: connectionId, datetime: expect.anything(), type: "connect" }; + expect(res.json).toHaveBeenCalledWith({ connections: expect.arrayContaining([connect]) }); + }); + + test('get all from session1', async () => { + await httpHandler.getAll(req, res); + const connect = { connectionId: connectionId, datetime: expect.anything(), type: "connect" }; + expect(res.json).toHaveBeenCalledWith({ messages: expect.arrayContaining([connect]), datetime: expect.anything() }); + }); + + test('post offer from session1', async () => { + const body = { connectionId: connectionId, sdp: testsdp, datetime: expect.anything(), type: "offer" }; + req.body = body; + await httpHandler.postOffer(req, res); + expect(res.sendStatus).toHaveBeenCalledWith(200); + }); + + test('get offer from session1', async () => { + await httpHandler.getOffer(req, res); + expect(res.json).toHaveBeenCalledWith({ offers: [] }); + }); + + test('get offer from session2', async () => { + await httpHandler.getOffer(req2, res); + expect(res.json).toHaveBeenCalledWith({ offers: [{ connectionId: connectionId, sdp: testsdp, polite: false, datetime: expect.anything(), type: "offer" }] }); + }); + + test('post answer from session2', async () => { + const body = { connectionId: connectionId, sdp: testsdp }; + req2.body = body; + await httpHandler.postAnswer(req2, res); + expect(res.sendStatus).toHaveBeenCalledWith(200); + }); + + test('get answer from session1', async () => { + await httpHandler.getAnswer(req, res); + expect(res.json).toHaveBeenCalledWith({ answers: [{ connectionId: connectionId, sdp: testsdp, datetime: expect.anything(), type: "answer" }] }); + }); + + test('get answer from session2', async () => { + await httpHandler.getAnswer(req2, res); + expect(res.json).toHaveBeenCalledWith({ answers: [] }); + }); + + test('post candidate from sesson1', async () => { + const body = { connectionId: connectionId, candidate: "testcandidate", sdpMLineIndex: 0, sdpMid: 0 }; + req.body = body; + await httpHandler.postCandidate(req, res); + expect(res.sendStatus).toHaveBeenCalledWith(200); + }); + + test('get candidate from session1', async () => { + await httpHandler.getCandidate(req, res); + expect(res.json).toHaveBeenCalledWith({ candidates: [] }); + }); + + test('get candidate from session2', async () => { + await httpHandler.getCandidate(req2, res); + expect(res.json).toHaveBeenCalledWith({ candidates: [{ connectionId: connectionId, candidate: "testcandidate", sdpMLineIndex: 0, sdpMid: 0, type: "candidate", datetime: expect.anything() }] }); + }); + + test('delete connection from session2', async () => { + const body = { connectionId: connectionId }; + req2.body = body; + await httpHandler.deleteConnection(req2, res); + expect(res.json).toHaveBeenCalledWith({ connectionId: connectionId }); + }); + + test('disconnection get from session1', async () => { + await httpHandler.getAll(req, res); + const disconnect = { connectionId: connectionId, datetime: expect.anything(), type: "disconnect" }; + expect(res.json).toHaveBeenCalledWith({ messages: expect.arrayContaining([disconnect]), datetime: expect.anything() }); + }); + + test('delete connection from session1', async () => { + const body = { connectionId: connectionId }; + req.body = body; + await httpHandler.deleteConnection(req, res); + expect(res.json).toHaveBeenCalledWith({ connectionId: connectionId }); + }); + + test('delete session1', async () => { + const req = getMockReq({ header: jest.fn(() => sessionId) }); + await httpHandler.deleteSession(req, res); + expect(res.sendStatus).toHaveBeenCalledWith(200); + }); + + test('delete session2', async () => { + const req2 = getMockReq({ header: jest.fn(() => sessionId2) }); + await httpHandler.deleteSession(req2, res); + expect(res.sendStatus).toHaveBeenCalledWith(200); + }); + + test('disconnection get when session2 disconnects before session1 answer', async () => { + httpHandler.reset("public"); + + await httpHandler.createSession(sessionId, res); + await httpHandler.createSession(sessionId2, res); + + await httpHandler.getAll(req, res); + expect(res.json).toHaveBeenLastCalledWith({ messages: [], datetime: expect.anything() }); + + const connectBody = { connectionId: connectionId }; + req.body = connectBody; + await httpHandler.createConnection(req, res); + + const offerBody = { connectionId: connectionId, sdp: testsdp, datetime: expect.anything(), type: "offer" }; + req.body = offerBody; + await httpHandler.postOffer(req, res); + + const offer = { connectionId: connectionId, sdp: testsdp, datetime: expect.anything(), type: "offer", polite: false }; + await httpHandler.getAll(req, res); + expect(res.json).toHaveBeenLastCalledWith({ messages: expect.not.arrayContaining([offer]), datetime: expect.anything() }); + await httpHandler.getAll(req2, res); + expect(res.json).toHaveBeenLastCalledWith({ messages: expect.arrayContaining([offer]), datetime: expect.anything() }); + + const deleteBody = { connectionId: connectionId }; + req2.body = deleteBody; + await httpHandler.deleteConnection(req, res); + await httpHandler.deleteSession(req, res); + expect(res.sendStatus).toHaveBeenLastCalledWith(200); + + const answerBody = { connectionId: connectionId, sdp: testsdp }; + req2.body = answerBody; + await httpHandler.postAnswer(req2, res); + + const disconnect = { connectionId: connectionId, type: "disconnect", datetime: expect.anything() }; + await httpHandler.getAll(req2, res); + expect(res.json).toHaveBeenLastCalledWith({ messages: expect.arrayContaining([disconnect]), datetime: expect.anything() }); + + await httpHandler.deleteSession(req2, res); + }); + + test('Timed out session2 deleted after session1 resends offer', async () => { + httpHandler.reset("public"); + + await httpHandler.createSession(sessionId, res); + await httpHandler.createSession(sessionId2, res); + + req.url = ""; + req2.url = ""; + await httpHandler.checkSessionId(req, res, next); + await httpHandler.checkSessionId(req2, res, next); + + await httpHandler.getAll(req, res); + expect(res.json).toHaveBeenLastCalledWith({ messages: [], datetime: expect.anything() }); + + const connectBody = { connectionId: connectionId }; + req.body = connectBody; + await httpHandler.createConnection(req, res); + + const offerBody = { connectionId: connectionId, sdp: testsdp, datetime: expect.anything(), type: "offer" }; + req.body = offerBody; + await httpHandler.postOffer(req, res); + + const offer = { connectionId: connectionId, sdp: testsdp, datetime: expect.anything(), type: "offer", polite: false }; + await httpHandler.getAll(req, res); + expect(res.json).toHaveBeenLastCalledWith({ messages: expect.not.arrayContaining([offer]), datetime: expect.anything() }); + await httpHandler.getAll(req2, res); + expect(res.json).toHaveBeenLastCalledWith({ messages: expect.arrayContaining([offer]), datetime: expect.anything() }); + + const answerBody = { connectionId: connectionId, sdp: testsdp }; + req2.body = answerBody; + await httpHandler.postAnswer(req2, res); + + // resend offer after answer to simulate PeerCandidate entering into failed state + req.body = offerBody; + await httpHandler.postOffer(req, res); + + // Wait a second and then checkSession for only session1 to force timeout of session2. + for (let i = 0; i < RetriesToForceTimeout + 1; ++i) + { + await httpHandler.checkSessionId(req, res, next); + await new Promise(resolve => setTimeout(resolve, 1000)); + } + + // Get all for session1 to trigger cleaning up associated session that timed out. + await httpHandler.getAll(req, res); + + // Check that we do have session1 still + await httpHandler.checkSessionId(req, res, next); + expect(res.sendStatus).toHaveBeenLastCalledWith(200); + + // Check that we no longer have session2 + await httpHandler.checkSessionId(req2, res, next); + expect(res.sendStatus).toHaveBeenLastCalledWith(404); + + await httpHandler.deleteSession(req, res); + }, 16000); + +test('Timed out sessions are deleted when other sessions check', async () => { + httpHandler.reset("public"); + + await httpHandler.createSession(sessionId, res); + await httpHandler.createSession(sessionId2, res); + await httpHandler.createSession(sessionId3, res); + + req.url = ""; + req2.url = ""; + req3.url = ""; + await httpHandler.checkSessionId(req, res, next); + await httpHandler.checkSessionId(req2, res, next); + await httpHandler.checkSessionId(req3, res, next); + + await httpHandler.getAll(req, res); + expect(res.json).toHaveBeenLastCalledWith({ messages: [], datetime: expect.anything() }); + + const connectBody = { connectionId: connectionId }; + req.body = connectBody; + await httpHandler.createConnection(req, res); + + const offerBody = { connectionId: connectionId, sdp: testsdp, datetime: expect.anything(), type: "offer" }; + req.body = offerBody; + await httpHandler.postOffer(req, res); + + const offer = { connectionId: connectionId, sdp: testsdp, datetime: expect.anything(), type: "offer", polite: false }; + await httpHandler.getAll(req, res); + expect(res.json).toHaveBeenLastCalledWith({ messages: expect.not.arrayContaining([offer]), datetime: expect.anything() }); + await httpHandler.getAll(req2, res); + expect(res.json).toHaveBeenLastCalledWith({ messages: expect.arrayContaining([offer]), datetime: expect.anything() }); + + const answerBody = { connectionId: connectionId, sdp: testsdp }; + req2.body = answerBody; + await httpHandler.postAnswer(req2, res); + + // Wait a second and then checkSession for only session3 to force timeout of session1 & session2. + for (let i = 0; i < RetriesToForceTimeout + 1; ++i) + { + await httpHandler.checkSessionId(req3, res, next); + await new Promise(resolve => setTimeout(resolve, 1000)); + } + + // Get all for session3 to trigger cleaning up sessions that timed out. + await httpHandler.getAll(req3, res); + + // Check that we do have session3 still + await httpHandler.checkSessionId(req3, res, next); + expect(res.sendStatus).toHaveBeenLastCalledWith(200); + + // Check that we do have session1 still + await httpHandler.checkSessionId(req, res, next); + expect(res.sendStatus).toHaveBeenLastCalledWith(404); + + // Check that we no longer have session2 + await httpHandler.checkSessionId(req2, res, next); + expect(res.sendStatus).toHaveBeenLastCalledWith(404); + + await httpHandler.deleteSession(req3, res); +}, 16000); +}); + +describe('http signaling test in private mode', () => { + const sessionId = "abcd1234"; + const sessionId2 = "abcd5678"; + const connectionId = "12345"; + const testsdp = "test sdp"; + + const { res, next, mockClear } = getMockRes(); + const req = getMockReq({ header: jest.fn(() => sessionId) }); + const req2 = getMockReq({ header: jest.fn(() => sessionId2) }); + + beforeAll(() => { + httpHandler.reset("private"); + }); + + beforeEach(() => { + mockClear(); + + httpHandler.checkSessionId(req, res, next); + httpHandler.checkSessionId(req2, res, next); + }); + + test('throw check has session', async () => { + httpHandler.checkSessionId(req, res, next); + expect(res.sendStatus).toHaveBeenCalledWith(404); + expect(next).not.toHaveBeenCalled(); + }); + + test('create session', async () => { + await httpHandler.createSession(sessionId, res); + expect(res.json).toHaveBeenCalledWith({ sessionId: sessionId }); + }); + + test('create session2', async () => { + await httpHandler.createSession(sessionId2, res); + expect(res.json).toHaveBeenCalledWith({ sessionId: sessionId2 }); + }); + + test('create connection from session1', async () => { + const body = { connectionId: connectionId }; + req.body = body; + await httpHandler.createConnection(req, res); + expect(res.json).toHaveBeenCalledWith({ connectionId: connectionId, polite: false, datetime: expect.anything(), type: "connect" }); + }); + + test('create connection from session2', async () => { + const body = { connectionId: connectionId }; + req2.body = body; + await httpHandler.createConnection(req2, res); + expect(res.json).toHaveBeenCalledWith({ connectionId: connectionId, polite: true, datetime: expect.anything(), type: "connect" }); + }); + + test('response status 400 if connecctionId does not set', async () => { + const req3 = getMockReq({ header: jest.fn(() => sessionId) }); + await httpHandler.createConnection(req3, res); + expect(res.status).toHaveBeenCalledWith(400); + expect(res.send).toHaveBeenCalledWith({ error: new Error(`connectionId is required`) }); + }); + + test('response status 400 if aleady used connection', async () => { + const sessionId3 = "session3"; + await httpHandler.createSession(sessionId3, res); + const body = { connectionId: connectionId }; + const req3 = getMockReq({ header: jest.fn(() => sessionId3) }); + req3.body = body; + await httpHandler.createConnection(req3, res); + expect(res.status).toHaveBeenCalledWith(400); + expect(res.send).toHaveBeenCalledWith({ error: new Error(`${connectionId}: This connection id is already used.`) }); + }); + + test('not connection get from session1', async () => { + await httpHandler.getConnection(req, res); + expect(res.json).toHaveBeenCalledWith({ connections: [{ connectionId: connectionId, datetime: expect.anything(), type: "connect" }] }); + }); + + test('post offer from session1', async () => { + const body = { connectionId: connectionId, sdp: testsdp, datetime: expect.anything(), type: "offer" }; + req.body = body; + await httpHandler.postOffer(req, res); + expect(res.sendStatus).toHaveBeenCalledWith(200); + }); + + test('get offer from session1', async () => { + await httpHandler.getOffer(req, res); + expect(res.json).toHaveBeenCalledWith({ offers: [] }); + }); + + test('get offer from session2', async () => { + await httpHandler.getOffer(req2, res); + expect(res.json).toHaveBeenCalledWith({ offers: [{ connectionId: connectionId, sdp: testsdp, polite: true, datetime: expect.anything(), type: "offer" }] }); + }); + + test('post answer from session2', async () => { + const body = { connectionId: connectionId, sdp: testsdp }; + req2.body = body; + await httpHandler.postAnswer(req2, res); + expect(res.sendStatus).toHaveBeenCalledWith(200); + }); + + test('get answer from session1', async () => { + await httpHandler.getAnswer(req, res); + expect(res.json).toHaveBeenCalledWith({ answers: [{ connectionId: connectionId, sdp: testsdp, datetime: expect.anything(), type: "answer" }] }); + }); + + test('get answer from session2', async () => { + await httpHandler.getAnswer(req2, res); + expect(res.json).toHaveBeenCalledWith({ answers: [] }); + }); + + test('post candidate from sesson1', async () => { + const body = { connectionId: connectionId, candidate: "testcandidate", sdpMLineIndex: 0, sdpMid: 0 }; + req.body = body; + await httpHandler.postCandidate(req, res); + expect(res.sendStatus).toHaveBeenCalledWith(200); + }); + + test('get candidate from session1', async () => { + await httpHandler.getCandidate(req, res); + expect(res.json).toHaveBeenCalledWith({ candidates: [] }); + }); + + test('get candidate from session2', async () => { + await httpHandler.getCandidate(req2, res); + expect(res.json).toHaveBeenCalledWith({ candidates: [{ connectionId: connectionId, candidate: "testcandidate", sdpMLineIndex: 0, sdpMid: 0, type: "candidate", datetime: expect.anything() }] }); + }); + + test('delete connection from session2', async () => { + const body = { connectionId: connectionId }; + req2.body = body; + await httpHandler.deleteConnection(req2, res); + expect(res.json).toHaveBeenCalledWith({ connectionId: connectionId }); + }); + + test('get connection from session1', async () => { + await httpHandler.getConnection(req, res); + expect(res.json).toHaveBeenCalledWith({ connections: [] }); + }); + + test('delete connection from session1', async () => { + const body = { connectionId: connectionId }; + req.body = body; + await httpHandler.deleteConnection(req, res); + expect(res.json).toHaveBeenCalledWith({ connectionId: connectionId }); + }); + + test('delete session1', async () => { + const req = getMockReq({ header: jest.fn(() => sessionId) }); + await httpHandler.deleteSession(req, res); + expect(res.sendStatus).toHaveBeenCalledWith(200); + }); + + test('delete session2', async () => { + const req2 = getMockReq({ header: jest.fn(() => sessionId2) }); + await httpHandler.deleteSession(req2, res); + expect(res.sendStatus).toHaveBeenCalledWith(200); + }); + + test('disconnection get when session2 disconnects before session1 answer', async () => { + httpHandler.reset("private"); + + await httpHandler.createSession(sessionId, res); + await httpHandler.createSession(sessionId2, res); + + await httpHandler.getAll(req, res); + expect(res.json).toHaveBeenLastCalledWith({ messages: [], datetime: expect.anything() }); + + const connectBody = { connectionId: connectionId }; + req.body = connectBody; + await httpHandler.createConnection(req, res); + req2.body = connectBody; + await httpHandler.createConnection(req2, res); + + const offerBody = { connectionId: connectionId, sdp: testsdp, datetime: expect.anything(), type: "offer" }; + req.body = offerBody; + await httpHandler.postOffer(req, res); + + const offer = { connectionId: connectionId, sdp: testsdp, datetime: expect.anything(), type: "offer", polite: true }; + await httpHandler.getAll(req, res); + expect(res.json).toHaveBeenLastCalledWith({ messages: expect.not.arrayContaining([offer]), datetime: expect.anything() }); + await httpHandler.getAll(req2, res); + expect(res.json).toHaveBeenLastCalledWith({ messages: expect.arrayContaining([offer]), datetime: expect.anything() }); + + const deleteBody = { connectionId: connectionId }; + req2.body = deleteBody; + await httpHandler.deleteConnection(req, res); + await httpHandler.deleteSession(req, res); + expect(res.sendStatus).toHaveBeenLastCalledWith(200); + + const answerBody = { connectionId: connectionId, sdp: testsdp }; + req2.body = answerBody; + await httpHandler.postAnswer(req2, res); + + const disconnect = { connectionId: connectionId, type: "disconnect", datetime: expect.anything() }; + await httpHandler.getAll(req2, res); + expect(res.json).toHaveBeenLastCalledWith({ messages: expect.arrayContaining([disconnect]), datetime: expect.anything() }); + + await httpHandler.deleteSession(req2, res); + }); +}); diff --git a/test/renderstreaming.postman_collection.json b/test/renderstreaming.postman_collection.json new file mode 100644 index 0000000..96287cb --- /dev/null +++ b/test/renderstreaming.postman_collection.json @@ -0,0 +1,1122 @@ +{ + "info": { + "_postman_id": "81f69f19-4db2-4baa-8210-c4a72f326a47", + "name": "renderstreaming", + "description": "# Introduction\nWhat does your API do?\n\n# Overview\nThings that the developers should know about\n\n# Authentication\nWhat is the preferred way of using the API?\n\n# Error Codes\nWhat errors and status codes can a user expect?\n\n# Rate limit\nIs there a limit to the number of requests an user can send?", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "837833" + }, + "item": [ + { + "name": "Create Session1", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "pm.test(\"The response has a valid JSON body\", function () {", + " pm.response.to.be.json;", + " var jsonData = pm.response.json();", + " pm.response.to.have.jsonBody(\"sessionId\");", + "});", + "pm.environment.set(\"session_Id\", pm.response.json().sessionId);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://{{url}}/signaling", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling" + ] + } + }, + "response": [] + }, + { + "name": "Create Session2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "pm.test(\"The response has a valid JSON body\", function () {", + " pm.response.to.be.json;", + " var jsonData = pm.response.json();", + " pm.response.to.have.jsonBody(\"sessionId\");", + "});", + "pm.environment.set(\"session_Id2\", pm.response.json().sessionId);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://{{url}}/signaling", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling" + ] + } + }, + "response": [] + }, + { + "name": "Create Connection", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "pm.test(\"The response has a valid JSON body\", function () {", + " pm.response.to.be.json;", + " var jsonData = pm.response.json();", + " pm.response.to.have.jsonBody(\"connectionId\");", + " pm.response.to.have.jsonBody(\"polite\");", + " console.log(jsonData);", + " pm.expect(jsonData.polite).to.be.true;", + "});", + "pm.environment.set(\"connection_id\", JSON.stringify(pm.response.json().connectionId));" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Session-Id", + "value": "{{session_Id}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n\t\"connectionId\": \"b808d31f-e22b-4ee8-85fb-4c4b587f8065\"\r\n}" + }, + "url": { + "raw": "http://{{url}}/signaling/connection", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling", + "connection" + ] + } + }, + "response": [] + }, + { + "name": "Post Offer from Session1", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Session-Id", + "value": "{{session_Id}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\t\n\t\"connectionId\": {{connection_id}},\n \"sdp\" : {{sdp}}\n}" + }, + "url": { + "raw": "http://{{url}}/signaling/offer", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling", + "offer" + ] + } + }, + "response": [ + { + "name": "Default", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "offer", + "host": [ + "offer" + ] + } + }, + "code": 200, + "_postman_previewlanguage": "Text", + "header": [], + "cookie": [], + "body": "" + } + ] + }, + { + "name": "Post Answer from Session2", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + }, + { + "key": "Session-Id", + "type": "text", + "value": "{{session_Id2}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"connectionId\": {{connection_id}},\n\t\"sdp\" : {{sdp}}\n}" + }, + "url": { + "raw": "http://{{url}}/signaling/answer", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling", + "answer" + ] + } + }, + "response": [ + { + "name": "Default", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "offer", + "host": [ + "offer" + ] + } + }, + "code": 200, + "_postman_previewlanguage": "Text", + "header": [], + "cookie": [], + "body": "" + } + ] + }, + { + "name": "Post Candidate from Session1", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + }, + { + "key": "Session-Id", + "type": "text", + "value": "{{session_Id}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"connectionId\": {{connection_id}},\n\t\"candidate\" : {{candidate}}\n}" + }, + "url": { + "raw": "http://{{url}}/signaling/candidate", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling", + "candidate" + ] + } + }, + "response": [ + { + "name": "Default", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "icecandidate", + "host": [ + "icecandidate" + ] + } + }, + "code": 200, + "_postman_previewlanguage": "Text", + "header": [], + "cookie": [], + "body": "" + } + ] + }, + { + "name": "Get Top index.html", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://{{url}}", + "protocol": "http", + "host": [ + "{{url}}" + ] + } + }, + "response": [] + }, + { + "name": "Get Connection", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "pm.test(\"The response has a valid JSON body\", function () {", + " pm.response.to.be.json;", + " pm.response.to.have.jsonBody(\"connections\");", + " var jsonData = pm.response.json();", + " console.log(jsonData);", + " pm.expect(jsonData.connections.length).to.be.above(0);", + " var connection = jsonData.connections[0];", + " pm.expect(connection.connectionId).to.be.not.null;", + "});", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Session-Id", + "type": "text", + "value": "{{session_Id}}" + } + ], + "url": { + "raw": "http://{{url}}/signaling/connection", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling", + "connection" + ] + } + }, + "response": [] + }, + { + "name": "Get Offer to Session1", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "pm.test(\"The response has a valid JSON body\", function () {", + " pm.response.to.be.json;", + " pm.response.to.have.jsonBody(\"offers\");", + " var jsonData = pm.response.json();", + " console.log(jsonData);", + " pm.expect(jsonData.offers.length).to.be.equal(0);", + "});", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Session-Id", + "type": "text", + "value": "{{session_Id}}" + } + ], + "url": { + "raw": "http://{{url}}/signaling/offer", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling", + "offer" + ] + } + }, + "response": [] + }, + { + "name": "Get Offer to Session2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "pm.test(\"The response has a valid JSON body\", function () {", + " pm.response.to.be.json;", + " pm.response.to.have.jsonBody(\"offers\");", + " var jsonData = pm.response.json();", + " console.log(jsonData);", + " pm.expect(jsonData.offers.length).to.be.above(0);", + " var offer = jsonData.offers[0];", + " pm.expect(offer.connectionId).to.be.not.null;", + " pm.expect(offer.sdp).to.be.not.null;", + " pm.expect(offer.polite).to.be.not.null;", + "});", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Session-Id", + "type": "text", + "value": "{{session_Id2}}" + } + ], + "url": { + "raw": "http://{{url}}/signaling/offer", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling", + "offer" + ] + } + }, + "response": [] + }, + { + "name": "Get Offer to Session1 fromtime={{datetime}}", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "pm.test(\"The response has a valid JSON body\", function () {", + " pm.response.to.be.json;", + " pm.response.to.have.jsonBody(\"offers\");", + " var jsonData = pm.response.json();", + " console.log(jsonData);", + " pm.expect(jsonData.offers.length).to.be.equal(0);", + "});", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "// datetime in the future", + "let datetime = Date.now() + 30;", + "pm.environment.set(\"datetime\", datetime);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Session-Id", + "type": "text", + "value": "{{session_Id}}" + } + ], + "url": { + "raw": "http://{{url}}/signaling/offer?fromtime={{datetime}}", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling", + "offer" + ], + "query": [ + { + "key": "fromtime", + "value": "{{datetime}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Get Answer to Session1", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "pm.test(\"The response has a valid JSON body\", function () {", + " pm.response.to.be.json;", + " pm.response.to.have.jsonBody(\"answers\");", + " var jsonData = pm.response.json();", + " console.log(jsonData);", + " pm.expect(jsonData.answers.length).to.be.above(0);", + " var answer = jsonData.answers[0];", + " pm.expect(answer.connectionId).to.be.not.null;", + " pm.expect(answer.sdp).to.be.not.null;", + "});", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Session-Id", + "type": "text", + "value": "{{session_Id}}" + } + ], + "url": { + "raw": "http://{{url}}/signaling/answer", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling", + "answer" + ] + } + }, + "response": [] + }, + { + "name": "Get Answer to Session2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "pm.test(\"The response has a valid JSON body\", function () {", + " pm.response.to.be.json;", + " pm.response.to.have.jsonBody(\"answers\");", + " var jsonData = pm.response.json();", + " console.log(jsonData);", + " pm.expect(jsonData.answers.length).to.be.equal(0);", + "});", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Session-Id", + "type": "text", + "value": "{{session_Id2}}" + } + ], + "url": { + "raw": "http://{{url}}/signaling/answer", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling", + "answer" + ] + } + }, + "response": [] + }, + { + "name": "Get Answer to Session1 fromtime={{datetime}}", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "pm.test(\"The response has a valid JSON body\", function () {", + " pm.response.to.be.json;", + " pm.response.to.have.jsonBody(\"answers\");", + " var jsonData = pm.response.json();", + " console.log(jsonData);", + " pm.expect(jsonData.answers.length).to.be.equal(0);", + "});", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "// datetime in the future", + "let datetime = Date.now() + 30;", + "pm.environment.set(\"datetime\", datetime);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Session-Id", + "type": "text", + "value": "{{session_Id}}" + } + ], + "url": { + "raw": "http://{{url}}/signaling/answer?fromtime={{datetime}}", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling", + "answer" + ], + "query": [ + { + "key": "fromtime", + "value": "{{datetime}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Get Candidate to Session2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "pm.test(\"The response has a valid JSON body\", function () {", + " pm.response.to.be.json;", + " pm.response.to.have.jsonBody(\"candidates\");", + " var jsonData = pm.response.json();", + " console.log(jsonData);", + " pm.expect(jsonData.candidates.length).to.be.above(0);", + " pm.expect(jsonData.candidates[0].connectionId).to.not.equal(null);", + " pm.expect(jsonData.candidates[0].candidates).to.not.equal(null);", + "});", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Session-Id", + "value": "{{session_Id2}}", + "type": "text" + } + ], + "url": { + "raw": "http://{{url}}/signaling/candidate", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling", + "candidate" + ] + } + }, + "response": [] + }, + { + "name": "Get Candidate to Session1 fromtime={{datetime}}", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "pm.test(\"The response has a valid JSON body\", function () {", + " pm.response.to.be.json;", + " pm.response.to.have.jsonBody(\"candidates\");", + " var jsonData = pm.response.json();", + " console.log(jsonData);", + " pm.expect(jsonData.candidates.length).to.be.equal(0);", + "});", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "// datetime in the future", + "let datetime = Date.now() + 30;", + "pm.environment.set(\"datetime\", datetime);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Session-Id", + "type": "text", + "value": "{{session_Id}}" + } + ], + "url": { + "raw": "http://{{url}}/signaling/candidate?fromtime={{datetime}}", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling", + "candidate" + ], + "query": [ + { + "key": "fromtime", + "value": "{{datetime}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Get All fromtime={{datetime}}", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "pm.test(\"The response has a valid JSON body\", function () {", + " pm.response.to.be.json;", + " pm.response.to.have.jsonBody(\"messages\");", + " pm.response.to.have.jsonBody(\"datetime\");", + " var jsonData = pm.response.json();", + " console.log(jsonData);", + " pm.expect(jsonData.messages.length).to.be.above(0);", + "});", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "// datetime in the future", + "let datetime = Date.now() + 30;", + "pm.environment.set(\"datetime\", datetime);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Session-Id", + "type": "text", + "value": "{{session_Id}}" + } + ], + "url": { + "raw": "http://{{url}}/signaling?fromtime={{datetime}}", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling" + ], + "query": [ + { + "key": "fromtime", + "value": "{{datetime}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Get Config", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "pm.test(\"The response has a valid JSON body\", function () {", + " pm.response.to.be.json;", + " var jsonData = pm.response.json();", + " console.log(jsonData);", + " pm.response.to.have.jsonBody(\"useWebSocket\");", + " pm.response.to.have.jsonBody(\"startupMode\");", + " pm.response.to.have.jsonBody(\"logging\");", + "});", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://{{url}}/config", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "config" + ] + } + }, + "response": [] + }, + { + "name": "Delete Connection", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "type": "text", + "value": "application/json" + }, + { + "key": "Session-Id", + "type": "text", + "value": "{{session_Id}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\t\n\t\"connectionId\": {{connection_id}}\n}" + }, + "url": { + "raw": "http://{{url}}/signaling/connection", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling", + "connection" + ] + } + }, + "response": [] + }, + { + "name": "Delete Session1", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Session-Id", + "type": "text", + "value": "{{session_Id}}" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://{{url}}/signaling", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling" + ] + } + }, + "response": [] + }, + { + "name": "Delete Session2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Session-Id", + "type": "text", + "value": "{{session_Id2}}" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://{{url}}/signaling", + "protocol": "http", + "host": [ + "{{url}}" + ], + "path": [ + "signaling" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "url", + "value": "localhost:8080", + "type": "string" + }, + { + "key": "session_Id", + "value": "", + "type": "string" + }, + { + "key": "sdp", + "value": "\"v=0\\r\\no=- 7060022371716902156 2 IN IP4 127.0.0.1\\r\\ns=-\\r\\nt=0 0\\r\\na=group:BUNDLE 0\\r\\na=msid-semantic: WMS 3fd630e8-9285-4f82-adbc-a7e31c334740\\r\\nm=audio 9 UDP\\/TLS\\/RTP\\/SAVPF 111 103 104 9 0 8 110 112 113 126\\r\\nc=IN IP4 0.0.0.0\\r\\na=rtcp:9 IN IP4 0.0.0.0\\r\\na=ice-ufrag:yOxE\\r\\na=ice-pwd:Ekoek1dl79NlWZS2dfmrW7Cr\\r\\na=ice-options:trickle\\r\\na=fingerprint:sha-256 02:F8:39:CE:EF:A7:77:B4:8D:56:8A:A1:C8:14:0C:1D:00:DF:99:14:ED:CE:05:4B:94:F9:EE:36:EE:4F:82:61\\r\\na=setup:actpass\\r\\na=mid:0\\r\\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\\r\\na=extmap:2 http:\\/\\/www.ietf.org\\/id\\/draft-holmer-rmcat-transport-wide-cc-extensions-01\\r\\na=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid\\r\\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\\r\\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\\r\\na=sendonly\\r\\na=msid:3fd630e8-9285-4f82-adbc-a7e31c334740 a573e4b8-b844-4ab9-b5b2-62c5bea85e9e\\r\\na=rtcp-mux\\r\\na=rtpmap:111 opus\\/48000\\/2\\r\\na=rtcp-fb:111 transport-cc\\r\\na=fmtp:111 minptime=10;useinbandfec=1\\r\\na=rtpmap:103 ISAC\\/16000\\r\\na=rtpmap:104 ISAC\\/32000\\r\\na=rtpmap:9 G722\\/8000\\r\\na=rtpmap:0 PCMU\\/8000\\r\\na=rtpmap:8 PCMA\\/8000\\r\\na=rtpmap:110 telephone-event\\/48000\\r\\na=rtpmap:112 telephone-event\\/32000\\r\\na=rtpmap:113 telephone-event\\/16000\\r\\na=rtpmap:126 telephone-event\\/8000\\r\\na=ssrc:2852212123 cname:GybrBKZh3U5xSqQq\\r\\na=ssrc:2852212123 msid:3fd630e8-9285-4f82-adbc-a7e31c334740 a573e4b8-b844-4ab9-b5b2-62c5bea85e9e\\r\\na=ssrc:2852212123 mslabel:3fd630e8-9285-4f82-adbc-a7e31c334740\\r\\na=ssrc:2852212123 label:a573e4b8-b844-4ab9-b5b2-62c5bea85e9e\\r\\n\"", + "type": "string" + }, + { + "key": "candidate", + "value": "\"\"", + "type": "string" + }, + { + "key": "connection_id", + "value": "", + "type": "string" + } + ] +} \ No newline at end of file diff --git a/test/websockethandler.test.ts b/test/websockethandler.test.ts new file mode 100644 index 0000000..f1de627 --- /dev/null +++ b/test/websockethandler.test.ts @@ -0,0 +1,190 @@ +import WS from "jest-websocket-mock"; +import Answer from "../src/class/answer"; +import Candidate from "../src/class/candidate"; +import Offer from "../src/class/offer"; +import * as wsHandler from '../src/class/websockethandler'; + +Date.now = jest.fn(() => 1482363367071); + +describe('websocket signaling test in public mode', () => { + let server: WS; + let client: WebSocket; + let client2: WebSocket; + const connectionId = "12345"; + const connectionId2 = "67890"; + const testsdp = "test sdp"; + + beforeAll(async () => { + wsHandler.reset("public"); + server = new WS("ws://localhost:1234", { jsonProtocol: true }); + client = new WebSocket("ws://localhost:1234"); + await server.connected; + client2 = new WebSocket("ws://localhost:1234"); + await server.connected; + }); + + afterAll(() => { + WS.clean(); + }); + + test('create session1', async () => { + expect(client).not.toBeNull(); + await wsHandler.add(client); + }); + + test('create session2', async () => { + expect(client2).not.toBeNull(); + await wsHandler.add(client2); + }); + + test('create connection from session1', async () => { + await wsHandler.onConnect(client, connectionId); + await expect(server).toReceiveMessage({ type: "connect", connectionId: connectionId, polite: true }); + expect(server).toHaveReceivedMessages([{ type: "connect", connectionId: connectionId, polite: true }]); + }); + + test('create connection from session2', async () => { + await wsHandler.onConnect(client2, connectionId2); + await expect(server).toReceiveMessage({ type: "connect", connectionId: connectionId2, polite: true }); + expect(server).toHaveReceivedMessages([{ type: "connect", connectionId: connectionId2, polite: true }]); + }); + + test('send offer from session1', async () => { + await wsHandler.onOffer(client, { connectionId: connectionId, sdp: testsdp }); + const receiveOffer = new Offer(testsdp, Date.now(), false); + await expect(server).toReceiveMessage({ from: connectionId, to: "", type: "offer", data: receiveOffer }); + expect(server).toHaveReceivedMessages([{ from: connectionId, to: "", type: "offer", data: receiveOffer }]); + }); + + test('send answer from session2', async () => { + await wsHandler.onAnswer(client2, { connectionId: connectionId, sdp: testsdp }); + const receiveAnswer = new Answer(testsdp, Date.now()); + await expect(server).toReceiveMessage({ from: connectionId, to: "", type: "answer", data: receiveAnswer }); + expect(server).toHaveReceivedMessages([{ from: connectionId, to: "", type: "answer", data: receiveAnswer }]); + }); + + test('send candidate from sesson1', async () => { + const msg = { connectionId: connectionId, candidate: "testcandidate", sdpMLineIndex: 0, sdpMid: "0" }; + await wsHandler.onCandidate(client, msg); + const receiveCandidate = new Candidate("testcandidate", 0, "0", Date.now()); + await expect(server).toReceiveMessage({ from: connectionId, to: "", type: "candidate", data: receiveCandidate }); + expect(server).toHaveReceivedMessages([{ from: connectionId, to: "", type: "candidate", data: receiveCandidate }]); + }); + + test('delete connection from session2', async () => { + await wsHandler.onDisconnect(client2, connectionId); + // disconnect send to client + await expect(server).toReceiveMessage({ type: "disconnect", connectionId: connectionId }); + // disconnect send to client2 + await expect(server).toReceiveMessage({ type: "disconnect", connectionId: connectionId }); + // server received total 2 disconnect messages + expect(server).toHaveReceivedMessages([{ type: "disconnect", connectionId: connectionId }, { type: "disconnect", connectionId: connectionId }]); + }); + + test('delete connection from session1', async () => { + await wsHandler.onDisconnect(client, connectionId); + await expect(server).toReceiveMessage({ type: "disconnect", connectionId: connectionId }); + expect(server).toHaveReceivedMessages([{ type: "disconnect", connectionId: connectionId }, { type: "disconnect", connectionId: connectionId }]); + }); + + test('delete session2', async () => { + expect(client).not.toBeNull(); + await wsHandler.remove(client2); + }); + + test('delete session1', async () => { + expect(client2).not.toBeNull(); + await wsHandler.remove(client); + }); +}); + +describe('websocket signaling test in private mode', () => { + let server: WS; + let client: WebSocket; + let client2: WebSocket; + const connectionId = "12345"; + const testsdp = "test sdp"; + + beforeAll(async () => { + wsHandler.reset("private"); + server = new WS("ws://localhost:1234", { jsonProtocol: true }); + client = new WebSocket("ws://localhost:1234"); + await server.connected; + client2 = new WebSocket("ws://localhost:1234"); + await server.connected; + }); + + afterAll(() => { + WS.clean(); + }); + + test('create session1', async () => { + expect(client).not.toBeNull(); + await wsHandler.add(client); + }); + + test('create session2', async () => { + expect(client2).not.toBeNull(); + await wsHandler.add(client2); + }); + + test('create connection from session1', async () => { + await wsHandler.onConnect(client, connectionId); + await expect(server).toReceiveMessage({ type: "connect", connectionId: connectionId, polite: false }); + expect(server).toHaveReceivedMessages([{ type: "connect", connectionId: connectionId, polite: false }]); + }); + + test('create connection from session2', async () => { + await wsHandler.onConnect(client2, connectionId); + await expect(server).toReceiveMessage({ type: "connect", connectionId: connectionId, polite: true }); + expect(server).toHaveReceivedMessages([{ type: "connect", connectionId: connectionId, polite: true }]); + }); + + test('send offer from session1', async () => { + await wsHandler.onOffer(client, { connectionId: connectionId, sdp: testsdp }); + const receiveOffer = new Offer(testsdp, Date.now(), true); + await expect(server).toReceiveMessage({ from: connectionId, to: "", type: "offer", data: receiveOffer }); + expect(server).toHaveReceivedMessages([{ from: connectionId, to: "", type: "offer", data: receiveOffer }]); + }); + + test('send answer from session2', async () => { + await wsHandler.onAnswer(client2, { connectionId: connectionId, sdp: testsdp }); + const receiveAnswer = new Answer(testsdp, Date.now()); + await expect(server).toReceiveMessage({ from: connectionId, to: "", type: "answer", data: receiveAnswer }); + expect(server).toHaveReceivedMessages([{ from: connectionId, to: "", type: "answer", data: receiveAnswer }]); + }); + + test('send candidate from sesson1', async () => { + const msg = { connectionId: connectionId, candidate: "testcandidate", sdpMLineIndex: 0, sdpMid: "0" }; + await wsHandler.onCandidate(client, msg); + const receiveCandidate = new Candidate("testcandidate", 0, "0", Date.now()); + await expect(server).toReceiveMessage({ from: connectionId, to: "", type: "candidate", data: receiveCandidate }); + expect(server).toHaveReceivedMessages([{ from: connectionId, to: "", type: "candidate", data: receiveCandidate }]); + }); + + test('delete connection from session2', async () => { + await wsHandler.onDisconnect(client2, connectionId); + // disconnect send to client + await expect(server).toReceiveMessage({ type: "disconnect", connectionId: connectionId }); + // disconnect send to client2 + await expect(server).toReceiveMessage({ type: "disconnect", connectionId: connectionId }); + // server received total 2 disconnect messages + expect(server).toHaveReceivedMessages([{ type: "disconnect", connectionId: connectionId }, { type: "disconnect", connectionId: connectionId }]); + }); + + test('delete connection from session1', async () => { + await wsHandler.onDisconnect(client, connectionId); + await expect(server).toReceiveMessage({ type: "disconnect", connectionId: connectionId }); + expect(server).toHaveReceivedMessages([{ type: "disconnect", connectionId: connectionId }, { type: "disconnect", connectionId: connectionId }]); + }); + + test('delete session2', async () => { + expect(client).not.toBeNull(); + await wsHandler.remove(client2); + }); + + test('delete session1', async () => { + expect(client2).not.toBeNull(); + await wsHandler.remove(client); + }); +}); diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..0a79a80 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "include": ["src/**/*"], + "extends": "./tsconfig.json" +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..04a5fd9 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "include": ["src/**/*", "test/**/*.ts"], + "exclude": ["node_modules", "**/*.spec.ts"], + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "lib": ["dom","es5"], + "sourceMap": true, + "outDir":"build", + "rootDir":"src" + } +} diff --git a/tsconfig.lint.json b/tsconfig.lint.json new file mode 100644 index 0000000..87c8094 --- /dev/null +++ b/tsconfig.lint.json @@ -0,0 +1,12 @@ +{ + "include": ["src/**/*", "test/**/*"], + "exclude": ["node_modules", "**/*.spec.ts"], + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "lib": ["dom","es5"], + "sourceMap": false, + "outDir":"build", + "rootDir":"src" + } +} \ No newline at end of file diff --git a/私有模式和公有模式区别.md b/私有模式和公有模式区别.md new file mode 100644 index 0000000..e55f2cc --- /dev/null +++ b/私有模式和公有模式区别.md @@ -0,0 +1,514 @@ +# WebRTC 信令模式说明:私有模式 vs 公有模式 + +## 📊 核心对比 + +| 特性 | **私有模式 (private)** | **公有模式 (public)** | +|------|----------------------|---------------------| +| **连接关系** | 1对多 (Host-Participants) | 多对多 (广播) | +| **角色分配** | 有明确的 host 和 participant 角色 | 所有客户端平等,无角色区分 | +| **信令路由** | 定向转发 (host ↔ participants) | 全局广播 (除发送者外所有人) | +| **连接组管理** | 使用 `connectionGroup` 管理 | 使用 `clients` 全局列表 | +| **适用场景** | 视频会议、主控-从控 | 直播、公开房间 | + +--- + +## 🔒 私有模式 (Private Mode) + +### 工作原理 + +``` +客户端A (Host) ←→ 服务器 ←→ 客户端B (Participant 1) + ←→ 客户端C (Participant 2) + ←→ 客户端D (Participant 3) +``` + +### 关键特性 + +#### 1. 角色分配机制 + +- **第一个**连接到服务器的客户端成为 **Host** +- **后续**连接的客户端成为 **Participants** +- 通过 `polite` 参数区分角色: + - `host`: `polite = false` + - `participant`: `polite = true` + +**代码实现** (`src/class/websockethandler.ts` 第 150-176 行): + +```typescript +function onConnect(ws: WebSocket, connectionId: string): void { + let polite = true; + + // 处理私有模式 + if (isPrivate) { + if (connectionGroup.has(connectionId)) { + const group = connectionGroup.get(connectionId); + // 已有host,新连接作为participant加入 + group.participants.add(ws); + console.log(`Participant joined connectionId: ${connectionId}, total participants: ${group.participants.size}`); + } else { + // 第一个连接成为host + connectionGroup.set(connectionId, { host: ws, participants: new Set() }); + polite = false; + console.log(`Host created connectionId: ${connectionId}`); + } + } + + // 发送连接成功消息(包含角色信息) + const role = polite ? 'participant' : 'host'; + ws.send(JSON.stringify({ type: "connect", connectionId: connectionId, polite: polite, role: role })); +} +``` + +#### 2. 信令转发规则 + +- **Host 发送** → 转发给**所有 participants** +- **Participant 发送** → 只转发给 **host** +- **Participants 之间不能直接通信** + +**代码实现** (`src/class/websockethandler.ts` 第 96-108 行): + +```typescript +function broadcastToGroup(connectionId: string, senderWs: WebSocket, message: any): void { + const group = connectionGroup.get(connectionId); + if (!group) return; + + // 如果发送者是host,转发给所有participants + if (senderWs === group.host) { + group.participants.forEach(participantWs => { + participantWs.send(JSON.stringify(message)); + }); + } else { + // 如果发送者是participant,转发给host + group.host.send(JSON.stringify(message)); + } +} +``` + +#### 3. Offer 信令处理 + +**代码实现** (`src/class/websockethandler.ts` 第 220-243 行): + +```typescript +function onOffer(ws: WebSocket, message: any): void { + const connectionId = message.connectionId as string; + const newOffer = new Offer(message.sdp, Date.now(), false); + + // 处理私有模式 + if (isPrivate) { + if (connectionGroup.has(connectionId)) { + const group = connectionGroup.get(connectionId); + if (group.host === ws) { + // host发送offer,转发给所有participants + newOffer.polite = true; + group.participants.forEach(participantWs => { + participantWs.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer })); + }); + } else { + // participant发送offer,转发给host + newOffer.polite = true; + group.host.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer })); + } + } + return; + } + // ... 公有模式处理 +} +``` + +#### 4. 断开连接处理 + +- **Host 断开** → 通知所有 participants,删除整个连接组 +- **Participant 断开** → 只通知 host,从 participants 中移除 + +**代码实现** (`src/class/websockethandler.ts` 第 114-142 行): + +```typescript +function remove(ws: WebSocket): void { + const connectionIds = clients.get(ws); + if (!connectionIds) return; + + connectionIds.forEach(connectionId => { + const group = connectionGroup.get(connectionId); + if (group) { + if (group.host === ws) { + // host断开连接,通知所有participants + group.participants.forEach(participantWs => { + participantWs.send(JSON.stringify({ type: "disconnect", connectionId: connectionId })); + }); + // 删除整个连接组 + connectionGroup.delete(connectionId); + } else { + // participant断开连接,从participants中移除并通知host + group.participants.delete(ws); + group.host.send(JSON.stringify({ type: "disconnect", connectionId: connectionId })); + } + } + console.log(`Remove connectionId: ${connectionId}`); + }); + + clients.delete(ws); +} +``` + +### 数据结构 + +```typescript +interface ConnectionGroup { + host: WebSocket; // 主机(第一个连接) + participants: Set; // 参与者集合(后续连接) +} + +const connectionGroup: Map = new Map(); +``` + +### 应用场景 + +- ✅ **视频会议** (1个主播 + 多个观众) +- ✅ **远程桌面控制** (1个主控 + 多个观察者) +- ✅ **直播互动** (主播与观众连麦) +- ✅ **教学系统** (教师 + 多个学生) +- ✅ **主从控制** (Unity 应用为主机,多个 Web 客户端为参与者) + +--- + +## 🌐 公有模式 (Public Mode) + +### 工作原理 + +``` +客户端A ←→ 服务器 ←→ 客户端B + ↕ (广播) ↕ +客户端C ←────────────→ 客户端D +``` + +### 关键特性 + +#### 1. 无角色区分 + +- 所有客户端地位平等 +- 没有 host/participant 的概念 +- `polite` 始终为 `true` + +#### 2. 全局广播机制 + +- 任何客户端发送的消息 → **广播给所有其他客户端** +- 支持 **多对多** 通信 +- 每个客户端既是发送者也是接收者 + +**代码实现** (`src/class/websockethandler.ts` 第 245-256 行): + +```typescript +function onOffer(ws: WebSocket, message: any): void { + const connectionId = message.connectionId as string; + const newOffer = new Offer(message.sdp, Date.now(), false); + + // ... 私有模式处理 + + // 公共模式:创建新的连接组(如果不存在) + if (!connectionGroup.has(connectionId)) { + connectionGroup.set(connectionId, { host: ws, participants: new Set() }); + } + + // 向所有其他客户端广播offer + clients.forEach((_v, k) => { + if (k == ws) { + return; // 跳过发送者 + } + k.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer })); + }); +} +``` + +#### 3. 对等通信 + +**消息流转示例**: +``` +A 发送消息 → B、C、D 都收到 +B 发送消息 → A、C、D 都收到 +C 发送消息 → A、B、D 都收到 +D 发送消息 → A、B、C 都收到 +``` + +### 应用场景 + +- ✅ **多人聊天室** (所有用户平等) +- ✅ **公开游戏房间** (多玩家对战) +- ✅ **协作编辑** (多人实时编辑) +- ✅ **广播直播** (单向推流,多人观看) +- ✅ **P2P 网状网络** (Full Mesh 拓扑) + +--- + +## 💻 启动方式 + +### 私有模式 + +```bash +# 使用 npm 脚本 +npm start -- -m private + +# 或直接运行 +node ./build/index.js -s -p 8080 -m private -k ./server.key -c ./server.cert + +# 开发模式 +npm run dev -- -m private +``` + +### 公有模式 + +```bash +# 使用 npm 脚本(默认模式) +npm start + +# 或明确指定 +npm start -- -m public + +# 或直接运行 +node ./build/index.js -s -p 8080 -m public -k ./server.key -c ./server.cert +``` + +### 参数说明 + +| 参数 | 说明 | 示例 | +|------|------|------| +| `-m` | 通信模式 | `-m private` 或 `-m public` | +| `-p` | 端口号 | `-p 8080` | +| `-s` | 启用 HTTPS | `-s` | +| `-k` | 密钥文件 | `-k ./server.key` | +| `-c` | 证书文件 | `-c ./server.cert` | +| `-t` | 信令类型 | `-t websocket` 或 `-t http` | + +--- + +## 📝 实际例子 + +### 私有模式示例 + +假设有 3 个客户端连接到同一个 `connectionId: "room1"`: + +**连接顺序**: +1. **客户端A** 先连接 → 成为 **Host** (`polite: false`) +2. **客户端B** 连接 → 成为 **Participant 1** (`polite: true`) +3. **客户端C** 连接 → 成为 **Participant 2** (`polite: true`) + +**消息流转**: +``` +A (Host) 发送消息 + → B 收到 + → C 收到 + +B (Participant) 发送消息 + → A 收到 + → C 收不到 ❌ + +C (Participant) 发送消息 + → A 收到 + → B 收不到 ❌ +``` + +**信令流程图**: +``` +B --[offer]--> 服务器 --[offer]--> A (Host) +A --[answer]--> 服务器 --[answer]--> B +A --[offer]--> 服务器 --[offer]--> C +C --[answer]--> 服务器 --[answer]--> A +``` + +### 公有模式示例 + +假设有 3 个客户端连接: + +**连接顺序**: +1. **客户端A** 连接 (`polite: true`) +2. **客户端B** 连接 (`polite: true`) +3. **客户端C** 连接 (`polite: true`) + +**消息流转**: +``` +A 发送消息 + → B 收到 ✅ + → C 收到 ✅ + +B 发送消息 + → A 收到 ✅ + → C 收到 ✅ + +C 发送消息 + → A 收到 ✅ + → B 收到 ✅ +``` + +**信令流程图**: +``` +A --[offer]--> 服务器 --[offer]--> B +A --[offer]--> 服务器 --[offer]--> C +B --[offer]--> 服务器 --[offer]--> A +B --[offer]--> 服务器 --[offer]--> C +C --[offer]--> 服务器 --[offer]--> A +C --[offer]--> 服务器 --[offer]--> B +``` + +--- + +## 🔍 代码中的模式判断 + +### 初始化模式 + +**`src/class/websockethandler.ts` 第 62-65 行**: + +```typescript +function reset(mode: string): void { + // 设置是否为私有模式 + isPrivate = mode == "private"; +} +``` + +### 模式判断逻辑 + +在各个信令处理函数中,通过 `isPrivate` 变量判断: + +```typescript +// Offer 处理 +if (isPrivate) { + // 私有模式逻辑:定向转发 +} else { + // 公有模式逻辑:全局广播 +} + +// Candidate 处理 +if (isPrivate) { + // 私有模式逻辑 + return; +} + +// Message 处理 +if (connectionGroup.has(connectionId)) { + const group = connectionGroup.get(connectionId); + if (group.host === ws) { + // host 发送 → 转发给 participants + } else { + // participant 发送 → 转发给 host + } +} +``` + +--- + +## 🎯 模式选择建议 + +### 选择私有模式的场景 + +- ✅ 需要明确的主从关系 +- ✅ 中心节点需要控制所有通信 +- ✅ 参与者之间不需要直接通信 +- ✅ 需要管理连接层级结构 +- ✅ 资源优化(减少不必要的 P2P 连接) + +### 选择公有模式的场景 + +- ✅ 所有参与者地位平等 +- ✅ 需要多对多通信 +- ✅ Full Mesh P2P 拓扑 +- ✅ 每个客户端都需要相互连接 +- ✅ 去中心化的应用场景 + +--- + +## 📊 性能对比 + +| 指标 | 私有模式 | 公有模式 | +|------|---------|---------| +| **连接数** | O(n) - 线性增长 | O(n²) - 平方增长 | +| **服务器负载** | 中等(需要路由转发) | 较低(主要是广播) | +| **客户端负载** | 较低(只与 host 建立连接) | 较高(需要与所有客户端建立连接) | +| **网络带宽** | 节省(定向传输) | 较高(广播传输) | +| **扩展性** | 好(适合大量 participants) | 受限(连接数随用户数平方增长) | + +--- + +## 🔧 测试方式 + +### 单元测试 + +项目中已包含完整的单元测试: + +**后端测试** (`test/websockethandler.test.ts`): + +```typescript +// 公有模式测试 +describe('websocket signaling test in public mode', () => { + beforeAll(async () => { + wsHandler.reset("public"); + // ... + }); +}); + +// 私有模式测试 +describe('websocket signaling test in private mode', () => { + beforeAll(async () => { + wsHandler.reset("private"); + // ... + }); +}); +``` + +**前端测试** (`client/test/signaling.test.js`): + +```javascript +// 公有模式测试 +describe.each([ + { mode: "mock" }, + { mode: "http" }, + { mode: "websocket" }, +])('signaling test in public mode', ({ mode }) => { + // ... +}); + +// 私有模式测试 +describe.each([ + { mode: "mock" }, + { mode: "http" }, + { mode: "websocket" }, +])('signaling test in private mode', ({ mode }) => { + // ... +}); +``` + +### 运行测试 + +```bash +# 运行后端测试 +npm test + +# 运行前端测试 +cd client +npm test +``` + +--- + +## 📚 相关文件 + +- **WebSocket 处理器**: `src/class/websockethandler.ts` +- **HTTP 处理器**: `src/class/httphandler.ts` +- **WebSocket 服务**: `src/websocket.ts` +- **前端信令**: `client/src/signaling.js` +- **主入口**: `src/index.ts` +- **后端测试**: `test/websockethandler.test.ts` +- **前端测试**: `client/test/signaling.test.js` + +--- + +## 💡 总结 + +| 模式 | 核心特点 | 最佳用途 | +|------|---------|---------| +| **私有模式** | 1对多、主从架构、定向转发 | 视频会议、远程控制、教学系统 | +| **公有模式** | 多对多、对等网络、全局广播 | 聊天室、多人游戏、协作编辑 | + +选择合适的模式取决于你的应用架构需求和通信拓扑结构。 + +--- + +**文档生成时间**: 2026-04-22 +**项目版本**: 3.1.0