diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 50409a9b..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "env": { - "es6": true, - "node": true, - "mocha": true - }, - "extends": "eslint:recommended", - "rules": { - "indent": [ - "error", - "tab", - { - "SwitchCase": 1 - } - ], - "no-console": "off", - "no-var": "error", - "prefer-const": "error", - "quotes": [ - "error", - "double", - { - "avoidEscape": true, - "allowTemplateLiterals": true - } - ], - "semi": [ - "error", - "always" - ] - }, - "parserOptions": { - "ecmaVersion": 2018 - } -} \ No newline at end of file diff --git a/.github/workflows/test-and-release.yml b/.github/workflows/test-and-release.yml new file mode 100644 index 00000000..afb92f31 --- /dev/null +++ b/.github/workflows/test-and-release.yml @@ -0,0 +1,90 @@ +name: Test and Release + +# Run this job on all pushes and pull requests +# as well as tags with a semantic version +on: + push: + branches: + - "*" + tags: + # normal versions + - "v[0-9]+.[0-9]+.[0-9]+" + # pre-releases + - "v[0-9]+.[0-9]+.[0-9]+-**" + pull_request: {} + +# Cancel previous PR/branch runs when a new commit is pushed +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +jobs: + # Performs quick checks before the expensive test runs + check-and-lint: + if: contains(github.event.head_commit.message, '[skip ci]') == false + + runs-on: ubuntu-latest + + steps: + - uses: ioBroker/testing-action-check@v1 + with: + node-version: "20.x" + # Uncomment the following line if your adapter cannot be installed using 'npm ci' + # install-command: 'npm install' + lint: true + + # Runs adapter tests on all supported node versions and OSes + adapter-tests: + if: contains(github.event.head_commit.message, '[skip ci]') == false + + runs-on: ${{ matrix.os }} + strategy: + matrix: + node-version: [20.x, 21.x, 18.x] + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - uses: ioBroker/testing-action-adapter@v1 + with: + node-version: ${{ matrix.node-version }} + os: ${{ matrix.os }} + # Uncomment the following line if your adapter cannot be installed using 'npm ci' + # install-command: 'npm install' + # TODO: To enable automatic npm releases, create a token on npmjs.org + # Enter this token as a GitHub secret (with name NPM_TOKEN) in the repository options + # Then uncomment the following block: + # Deploys the final package to NPM + deploy: + needs: [check-and-lint, adapter-tests] + + # Trigger this step only when a commit on any branch is tagged with a version number + if: | + contains(github.event.head_commit.message, '[skip ci]') == false && + github.event_name == 'push' && + startsWith(github.ref, 'refs/tags/v') + + runs-on: ubuntu-latest + + # Write permissions are required to create Github releases + permissions: + contents: write + + steps: + - uses: ioBroker/testing-action-deploy@v1 + with: + node-version: "20.x" + # Uncomment the following line if your adapter cannot be installed using 'npm ci' + # install-command: 'npm install' + npm-token: ${{ secrets.NPM_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + # When using Sentry for error reporting, Sentry can be informed about new releases + # To enable create a API-Token in Sentry (User settings, API keys) + # Enter this token as a GitHub secret (with name SENTRY_AUTH_TOKEN) in the repository options + # Then uncomment and customize the following block: + sentry: true + sentry-token: ${{ secrets.SENTRY_AUTH_TOKEN }} + sentry-project: "iobroker-vw-connect" + sentry-version-prefix: "iobroker.vw-connect" + # If your sentry project is linked to a GitHub repository, you can enable the following option + # sentry-github-integration: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..dc5f07f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +.git +.idea +*.code-workspace +node_modules +nbproject + +# npm package files +iobroker.*.tgz + +Thumbs.db + +# i18n intermediate files +admin/i18n/flat.txt +admin/i18n/*/flat.txt +.DS_Store diff --git a/.npmignore b/.npmignore index ed16ee5d..0420f2fd 100644 --- a/.npmignore +++ b/.npmignore @@ -25,4 +25,5 @@ package-lock.json admin/i18n # maintenance scripts -maintenance/** \ No newline at end of file +maintenance/** +tsconfig.json diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..515bcd4f --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +package.json +package-lock.json \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 00000000..a2c24871 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,9 @@ +module.exports = { + semi: true, + trailingComma: "all", + singleQuote: false, + printWidth: 120, + useTabs: false, + tabWidth: 2, + endOfLine: "lf", +}; diff --git a/.releaseconfig.json b/.releaseconfig.json new file mode 100644 index 00000000..e7d09e24 --- /dev/null +++ b/.releaseconfig.json @@ -0,0 +1,3 @@ +{ + "plugins": ["iobroker", "license", "manual-review"] +} diff --git a/.travis.yml b/.travis.yml index d473faf5..baa59b04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,11 @@ os: - linux - osx - windows - language: node_js node_js: - - '8' - - '10' - + - '12' + - '14' + - '16' env: - CXX=g++-4.8 addons: @@ -15,17 +14,16 @@ addons: sources: - ubuntu-toolchain-r-test packages: - - g++-4.9 - + - g++-6 before_install: - - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then CC=gcc-4.9; fi' - - 'if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then CC=g++-4.9; fi' + - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then CC=gcc-6; fi' + - 'if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then CC=g++-6; fi' before_script: - export NPMVERSION=$(echo "$($(which npm) -v)"|cut -c1) - 'if [[ $NPMVERSION == 5 ]]; then npm install -g npm; fi' - npm -v script: - - npm run test:package - - npm run test:unit - - export DEBUG=testing:* - - npm run test:integration + - 'npm run test:package' + - 'npm run test:unit' + - 'export DEBUG=testing:*' + - 'npm run test:integration' diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index a8d77849..00000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "recommendations": [ - "dbaeumer.vscode-eslint" - ] -} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index be07e3e1..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "attach", - "name": "An den Prozess anfügen", // Name, der im VSCode Auswahlmenü neben dem grünen Play angezeigt wird - "address": "127.0.0.1", // Adresse, an dem der node.js Prozess läuft (bei Remote Debug, der Remote-Rechner) - "port": 9229 // Port, auf dem der node.js Debugger lauscht, der mit node --debug-brk ... gestartet wird - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 088bdc8a..7a73a41b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,2 @@ { - "eslint.enable": true } \ No newline at end of file diff --git a/LICENSE b/LICENSE index 9b2e53f0..f08d03ff 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 ta2k +Copyright (c) 2019-2026 ta2k Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 0b3ac4aa..75c9a09a 100644 --- a/README.md +++ b/README.md @@ -1,207 +1,246 @@ ![Logo](admin/vw-connect.png) - # ioBroker.vw-connect - - [![NPM version](http://img.shields.io/npm/v/iobroker.vw-connect.svg)](https://www.npmjs.com/package/iobroker.vw-connect) - [![Downloads](https://img.shields.io/npm/dm/iobroker.vw-connect.svg)](https://www.npmjs.com/package/iobroker.vw-connect) - [![Dependency Status](https://img.shields.io/david/ta2k/iobroker.vw-connect.svg)](https://david-dm.org/ta2k/iobroker.vw-connect) - [![Known Vulnerabilities](https://snyk.io/test/github/ta2k/ioBroker.vw-connect/badge.svg)](https://snyk.io/test/github/ta2k/ioBroker.vw-connect) - - [![NPM](https://nodei.co/npm/iobroker.vw-connect.png?downloads=true)](https://nodei.co/npm/iobroker.vw-connect/) - - **Tests:**: [![Travis-CI](http://img.shields.io/travis/ta2k/ioBroker.vw-connect/master.svg)](https://travis-ci.org/ta2k/ioBroker.vw-connect) - - ## vw-connect adapter for ioBroker - - Adapter for VW We Connect and Skoda Connect - - ## Usage - Use the state under remote control to control your car remotly. - - ## Status fields Explanation - - - '0x0101010002.0x0101010002': //distanceCovered - - '0x0204FFFFFF.0x02040C0001': //adBlueInspectionData_km - - '0x0203FFFFFF.0x0203010001': //oilInspectionData_km - - '0x0203FFFFFF.0x0203010002': //oilInspectionData_days - - '0x0203FFFFFF.0x0203010003': //serviceInspectionData_km - - '0x0203FFFFFF.0x0203010004': //serviceInspectionData_days - - '0x030101FFFF.0x0301010001': //status_parking_light_off - - '0x030103FFFF.0x0301030001': //parking brake - - '0x030103FFFF.0x0301030007': //fuel type - - '0x030103FFFF.0x030103000A': //fuel level - - '0x030103FFFF.0x0301030006': //fuel range - - '0x030103FFFF.0x0301030009': //secondary_typ - erst ab Modelljahr 2018 - - '0x030103FFFF.0x0301030002': //soc_ok - - '0x030103FFFF.0x0301030008': //secondary_range - erst ab Modelljahr 2018 - - '0x030103FFFF.0x0301030005': //hybrid_range - erst ab Modelljahr 2018 - - 1 = open 2 = Locked 3 = Closed - //door1 - front/left - - '0x030104FFFF.0x0301040001': - //door2 - rear/left - - '0x030104FFFF.0x0301040004': - //door3 - front/right - - '0x030104FFFF.0x0301040007': - //door4 - rear/right - - '0x030104FFFF.0x030104000A': - //door5 - rear - - '0x030104FFFF.0x030104000D': - //door6 - hood - - '0x030104FFFF.0x0301040010': - //window1 - front/left - - '0x030105FFFF.0x0301050001': - //window2 - rear/left - - '0x030105FFFF.0x0301050003': - //window3 - front/right - - '0x030105FFFF.0x0301050005': - //window4 - rear/right - - '0x030105FFFF.0x0301050007': - //window4 - roof window - - '0x030105FFFF.0x030105000B': - - - 1MAINTENANCE_INTERVAL_DISTANCE_TO_OIL_CHANGE", 0, "0x0203010001"); - 2MAINTENANCE_INTERVAL_TIME_TO_OIL_CHANGE", 1, "0x0203010002"); - 3MAINTENANCE_INTERVAL_DISTANCE_TO_INSPECTION", 2, "0x0203010003"); - 4MAINTENANCE_INTERVAL_TIME_TO_INSPECTION", 3, "0x0203010004"); - 5WARNING_OIL_CHANGE", 4, "0x0203010005"); - 6MAINTENANCE_INTERVAL_ALARM_INSPECTION", 5, "0x0203010006"); - 7MAINTENANCE_INTERVAL_MONTHLY_MILEAGE", 6, "0x0203010007"); - 8MAINTENANCE_INTERVAL_AD_BLUE_RANGE", 7, "0x02040C0001"); - 9OIL_LEVEL_AMOUNT_IN_LITERS", 8, "0x0204040001"); - 10OIL_LEVEL_MINIMUM_WARNING", 9, "0x0204040002"); - 11OIL_LEVEL_DIPSTICK_PERCENTAGE", 10, "0x0204040003"); - 12LIGHT_STATUS", 11, "0x0301010001"); - 13TOTAL_RANGE", 12, "0x0301030005"); - 14FUEL_LEVEL_IN_PERCENTAGE", 13, "0x030103000A"); - 15CNG_LEVEL_IN_PERCENTAGE", 14, "0x030103000D"); - 16LOCK_STATE_LEFT_FRONT_DOOR", 15, "0x0301040001"); - 17OPEN_STATE_LEFT_FRONT_DOOR", 16, "0x0301040002"); - 18SAFETY_STATE_LEFT_FRONT_DOOR", 17, "0x0301040003"); - 19LOCK_STATE_LEFT_REAR_DOOR", 18, "0x0301040004"); - 20OPEN_STATE_LEFT_REAR_DOOR", 19, "0x0301040005"); - 21SAFETY_STATE_LEFT_REAR_DOOR", 20, "0x0301040006"); - 22LOCK_STATE_RIGHT_FRONT_DOOR", 21, "0x0301040007"); - 23OPEN_STATE_RIGHT_FRONT_DOOR", 22, "0x0301040008"); - 24SAFETY_STATE_RIGHT_FRONT_DOOR", 23, "0x0301040009"); - 25LOCK_STATE_RIGHT_REAR_DOOR", 24, "0x030104000A"); - 26OPEN_STATE_RIGHT_REAR_DOOR", 25, "0x030104000B"); - 27SAFETY_STATE_RIGHT_REAR_DOOR", 26, "0x030104000C"); - 28LOCK_STATE_TRUNK_LID", 27, "0x030104000D"); - 29OPEN_STATE_TRUNK_LID", 28, "0x030104000E"); - 30SAFETY_STATE_TRUNK_LID", 29, "0x030104000F"); - 31LOCK_STATE_HOOD", 30, "0x0301040010"); - 32OPEN_STATE_HOOD", 31, "0x0301040011"); - 33SAFETY_STATE_HOOD", 32, "0x0301040012"); - 34STATE_LEFT_FRONT_WINDOW", 33, "0x0301050001"); - 35POSITION_LEFT_FRONT_WINDOW", 34, "0x0301050002"); - 36STATE_LEFT_REAR_WINDOW", 35, "0x0301050003"); - 37POSITION_LEFT_REAR_WINDOW", 36, "0x0301050004"); - 38STATE_RIGHT_FRONT_WINDOW", 37, "0x0301050005"); - 39POSITION_RIGHT_FRONT_WINDOW", 38, "0x0301050006"); - 40STATE_RIGHT_REAR_WINDOW", 39, "0x0301050007"); - 41POSITION_RIGHT_REAR_WINDOW", 40, "0x0301050008"); - 42STATE_CONVERTIBLE_TOP", 41, "0x0301050009"); - 43POSITION_CONVERTIBLE_TOP", 42, "0x030105000A"); - 44STATE_SUN_ROOF_MOTOR_COVER", 43, "0x030105000B"); - 45POSITION_SUN_ROOF_MOTOR_COVER", 44, "0x030105000C"); - 46STATE_SUN_ROOF_REAR_MOTOR_COVER_3", 45, "0x030105000D"); - 47POSITION_SUN_ROOF_REAR_MOTOR_COVER_3", 46, "0x030105000E"); - 48STATE_SERVICE_FLAP", 47, "0x030105000F"); - 49POSITION_SERVICE_FLAP", 48, "0x0301050010"); - 50STATE_SPOILER", 49, "0x0301050011"); - 51POSITION_SPOILER", 50, "0x0301050012"); - 52UTC_TIME_STATUS", 51, "0x0101010001"); - 53KILOMETER_STATUS", 52, "0x0101010002"); - 54PRIMARY_RANGE", 53, "0x0301030006"); - 55PRIMARY_DRIVE", 54, "0x0301030007"); - 56SECONDARY_RANGE", 55, "0x0301030008"); - 57SECONDARY_DRIVE", 56, "0x0301030009"); - 58STATE_OF_CHARGE", 57, "0x0301030002"); - 59TEMPERATURE_OUTSIDE", 58, "0x0301020001"); - 60PARKING_BRAKE", 59, "0x0301030001"); - 61TYRE_PRESSURE_LEFT_FRONT_CURRENT_VALUE", 60, "0x0301060001"); - 62TYRE_PRESSURE_LEFT_FRONT_DESIRED_VALUE", 61, "0x0301060002"); - 63TYRE_PRESSURE_LEFT_REAR_CURRENT_VALUE", 62, "0x0301060003"); - 64TYRE_PRESSURE_LEFT_REAR_DESIRED_VALUE", 63, "0x0301060004"); - 65TYRE_PRESSURE_RIGHT_FRONT_CURRENT_VALUE", 64, "0x0301060005"); - 66TYRE_PRESSURE_RIGHT_FRONT_DESIRED_VALUE", 65, "0x0301060006"); - 67TYRE_PRESSURE_RIGHT_REAR_CURRENT_VALUE", 66, "0x0301060007"); - 68TYRE_PRESSURE_RIGHT_REAR_DESIRED_VALUE", 67, "0x0301060008"); - 69TYRE_PRESSURE_SPARE_TYRE_CURRENT_VALUE", 68, "0x0301060009"); - 70TYRE_PRESSURE_SPARE_TYRE_DESIRED_VALUE", 69, "0x030106000A"); - 71TYRE_PRESSURE_LEFT_FRONT_TYRE_DIFFERENCE", 70, "0x030106000B"); - 72TYRE_PRESSURE_LEFT_REAR_TYRE_DIFFERENCE", 71, "0x030106000C"); - 73TYRE_PRESSURE_RIGHT_FRONT_TYRE_DIFFERENCE", 72, "0x030106000D"); - 74TYRE_PRESSURE_RIGHT_REAR_TYRE_DIFFERENCE", 73, "0x030106000E"); - 75TYRE_PRESSURE_SPARE_TYRE_DIFFERENCE", 74, "0x030106000F"); - - ## Changelog - - ### 0.0.6 - * add audi - - ### 0.0.5 - * add honk and flash, fix address format - - ### 0.0.4 - * add Skoda support - - ### 0.0.3 - * (ta2k) add more information - * (ta2k) add remote controls - - ### 0.0.2 - * (ta2k) add car status capturing - - ### 0.0.1 - * (ta2k) initial release - - ## License - MIT License - - Copyright (c) 2019 ta2k - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. \ No newline at end of file + +# ioBroker.vw-connect + +[![NPM version](http://img.shields.io/npm/v/iobroker.vw-connect.svg)](https://www.npmjs.com/package/iobroker.vw-connect) +[![Downloads](https://img.shields.io/npm/dm/iobroker.vw-connect.svg)](https://www.npmjs.com/package/iobroker.vw-connect) +[![Dependency Status](https://img.shields.io/david/ta2k/iobroker.vw-connect.svg)](https://david-dm.org/ta2k/iobroker.vw-connect) +[![Known Vulnerabilities](https://snyk.io/test/github/ta2k/ioBroker.vw-connect/badge.svg)](https://snyk.io/test/github/ta2k/ioBroker.vw-connect) + +[![NPM](https://nodei.co/npm/iobroker.vw-connect.png?downloads=true)](https://nodei.co/npm/iobroker.vw-connect/) + +**Tests:**: [![Travis-CI](http://img.shields.io/travis/ta2k/ioBroker.vw-connect/master.svg)](https://travis-ci.org/ta2k/ioBroker.vw-connect) + +## vw-connect adapter for ioBroker + +Adapter for VW We Connect, We Connect ID, We Charge, myAudi, Skoda Connect, Seat Connect and We Connect Go + +Please update your system on Node 10. + + +## Usage + +Use the state under remote control to control your car remotely. +Normale refresh is the polling interval to receive data from the VAG Cloud +Force refresh is for non E-Cars to enforce a refresh this number is limited by VAG until the car is turn on again. +Trip data is only available for non E-Cars. + +You can set climatisaton temperature in +.climater.settings.targetTemperature.content + +## Discussion and Questions + + + +## Status fields Explanation + +### List of entries + +``` + +``` + +## Changelog +### 0.7.15 (2025-11-26) +- fix vw refresh token + +### 0.7.14 (2025-11-25) +- fix vw id login + +### 0.7.13 (2025-11-09) +- fix for skoda login + +### 0.7.12 (2025-05-05) + +- fix for skoda refresh token +- fix for ventilation activation +- add new not supported endpoints + +### 0.7.9 (2025-03-20) + +- fix for id wall charger + +### 0.7.7 (2025-03-02) + +- fix for skoda auxiliaryheating and duration +- fix for skoda lock/unlock + +### 0.7.6 (2025-02-28) + +- fix for charging status updates only at startup +- fix for skoda ismoving state + +### 0.7.3 (2025-02-26) + +- fix for set setTemperature +- fix for Skoda unlock lock + +### 0.7.0 (2025-02-25) + +- fix for skoda and seat +- State structure changed completly please delete old states under Objects + +### 0.6.1 (2024-10-01) + +- fix for skoda login + +### 0.6.0 (2024-04-11) + +- add additonal cupra states + +### 0.5.4 (2024-03-17) + +- fix door window states + +### 0.4.1 + +- Fix VW Status Update + +### 0.0.65 + +- Fix Cupra login + +### 0.0.63 + +- Fix VW/Skoda etron login + +### 0.0.62 + +- Fix Audi etron login + +### 0.0.61 + +- Fix ID login + +### 0.0.60 + +- Minor improvements. WeCharge Minimum interval is now 15 minutes + +### 0.0.55 + +- fix id status update + +### 0.0.51 + +- fix audi etron login + +### 0.0.48 + +- fix login, fix audi update, add limit for wallbox + +### 0.0.43 + +- increase refresh token timeouts + +### 0.0.42 + +- fix skoda login + +### 0.0.40 + +- add climate v3 for newer cars. Add Powerpass and Seat Elli + +### 0.0.39 + +- fix id login + +### 0.0.36 + +- add Skoda Enyaq support + +### 0.0.35 + +- add nodeJS v10 compatibility + +### 0.0.34 + +- add auto accept of new privacy consent + +### 0.0.32 + +- correct selection of last recent trips + +### 0.0.31 + +- enable multiple selection of trip types + +### 0.0.30 + +- fix mutiple car problem, add VWv2 mode at the moment there is no different between VW and VWv2 + +### 0.0.29 + +- fix skoda refreshToken, smaller improvements + +### 0.0.26 + +- bugfixes + +### 0.0.25 + +- add we charge + +### 0.0.24 + +- add remote state update + +### 0.0.23 + +- add Seat and new climatisation v2 + +### 0.0.22 + +- calculate outside temperatur in °C also for Skoda and Audi + +### 0.0.21 + +- add remotes for id + +### 0.0.20 + +- fix audi login, add ID login + +### 0.0.19 + +- save status objects in state by id instead of consecutive numbers + +### 0.0.18 + +- fix battery status for 2020 models + +### 0.0.17 + +- add support for 2020 models + +### 0.0.16 + +- fix js.controller 3 problems + +### 0.0.11 + +- fix audi bug with multiple vehicles +- hide status update error if feature is not available + +## License + +MIT License + +Copyright (c) 2019-2026 ta2k + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/admin/custom_m.html b/admin/custom_m.html deleted file mode 100644 index 6b30455e..00000000 --- a/admin/custom_m.html +++ /dev/null @@ -1,44 +0,0 @@ - - - \ No newline at end of file diff --git a/admin/index_m.html b/admin/index_m.html index c11686e7..2ac5c427 100644 --- a/admin/index_m.html +++ b/admin/index_m.html @@ -1,121 +1,191 @@ - - - - + + + + - - + + - - - + + + - - - + + + - - + // This will be called by the admin adapter when the user presses the save button + function save(callback) { + // example: select elements with class=value and build settings object + var obj = {}; + $(".value").each(function () { + var $this = $(this); + if ($this.attr("type") === "checkbox") { + obj[$this.attr("id")] = $this.prop("checked"); + } else { + obj[$this.attr("id")] = $this.val(); + } + }); + callback(obj); + } + + - -
-
-
- -
-
- - - - -
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
+ +
+
+
+ +
+
-
-
- - -
-
- - Lizenzinformationen laden -
-
+ -
-
- - Position in Adresse umwandeln (nur Auswählen wenn es genutzt wird) -
-
+ +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + Nur den letzten Trip laden +
+
+
+
+ + + + Auswahl der kompletten abzurufenden Fahrdaten (Trips) +
+
+ + +
+
+ + +
+
+
+
+ + Lizenzinformationen laden +
+
+
+
+ + Position in Adresse umwandeln (nur Auswählen, wenn es genutzt wird) +
+
+
+
+ + Rohe JSON speichern
- +
+
+ diff --git a/admin/words.js b/admin/words.js index 1676a110..aa7f7286 100644 --- a/admin/words.js +++ b/admin/words.js @@ -1,41 +1,42 @@ +//eslint-disable-next-line no-unused-vars /*global systemDictionary:true */ "use strict"; systemDictionary = { - "vw-connect adapter settings": { - "en": "Adapter settings for vw-connect", - "de": "Adaptereinstellungen für vw-connect", - "ru": "Настройки адаптера для vw-connect", - "pt": "Configurações do adaptador para vw-connect", - "nl": "Adapterinstellingen voor vw-connect", - "fr": "Paramètres d'adaptateur pour vw-connect", - "it": "Impostazioni dell'adattatore per vw-connect", - "es": "Ajustes del adaptador para vw-connect", - "pl": "Ustawienia adaptera dla vw-connect", - "zh-cn": "vw-connect的适配器设置" - }, - "option1": { - "en": "option1", - "de": "Option 1", - "ru": "Опция 1", - "pt": "Opção 1", - "nl": "Optie 1", - "fr": "Option 1", - "it": "opzione 1", - "es": "Opción 1", - "pl": "opcja 1", - "zh-cn": "选项1" - }, - "option2": { - "en": "option2", - "de": "Option 2", - "ru": "option2", - "pt": "opção 2", - "nl": "Optie 2", - "fr": "Option 2", - "it": "opzione 2", - "es": "opcion 2", - "pl": "Opcja 2", - "zh-cn": "选项2" - } -}; \ No newline at end of file + "vw-connect adapter settings": { + en: "Adapter settings for vw-connect", + de: "Adaptereinstellungen für vw-connect", + ru: "Настройки адаптера для vw-connect", + pt: "Configurações do adaptador para vw-connect", + nl: "Adapterinstellingen voor vw-connect", + fr: "Paramètres d'adaptateur pour vw-connect", + it: "Impostazioni dell'adattatore per vw-connect", + es: "Ajustes del adaptador para vw-connect", + pl: "Ustawienia adaptera dla vw-connect", + "zh-cn": "vw-connect的适配器设置", + }, + option1: { + en: "option1", + de: "Option 1", + ru: "Опция 1", + pt: "Opção 1", + nl: "Optie 1", + fr: "Option 1", + it: "opzione 1", + es: "Opción 1", + pl: "opcja 1", + "zh-cn": "选项1", + }, + option2: { + en: "option2", + de: "Option 2", + ru: "option2", + pt: "opção 2", + nl: "Optie 2", + fr: "Option 2", + it: "opzione 2", + es: "opcion 2", + pl: "Opcja 2", + "zh-cn": "选项2", + }, +}; diff --git a/eslint.config.cjs b/eslint.config.cjs new file mode 100644 index 00000000..5b6475c2 --- /dev/null +++ b/eslint.config.cjs @@ -0,0 +1,52 @@ +const globals = require("globals"); +const js = require("@eslint/js"); + +const { + FlatCompat, +} = require("@eslint/eslintrc"); + +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all +}); + +module.exports = [{ + ignores: ["**/.prettierrc.js", "**/.eslintrc.js"], +}, ...compat.extends("eslint:recommended"), { + plugins: {}, + + languageOptions: { + globals: { + ...globals.node, + ...globals.mocha, + }, + + ecmaVersion: 2020, + sourceType: "commonjs", + }, + + rules: { + indent: ["error", 2, { + SwitchCase: 1, + }], + + "no-console": "off", + + "no-unused-vars": ["error", { + ignoreRestSiblings: true, + argsIgnorePattern: "^_", + }], + + "no-var": "error", + "no-trailing-spaces": "error", + "prefer-const": "error", + + quotes: ["error", "double", { + avoidEscape: true, + allowTemplateLiterals: true, + }], + + semi: ["error", "always"], + }, +}]; \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index 95f92e6a..00000000 --- a/gulpfile.js +++ /dev/null @@ -1,475 +0,0 @@ -/*! - * ioBroker gulpfile - * Date: 2019-01-28 - */ -"use strict"; - -const gulp = require("gulp"); -const fs = require("fs"); -const pkg = require("./package.json"); -const iopackage = require("./io-package.json"); -const version = (pkg && pkg.version) ? pkg.version : iopackage.common.version; -const fileName = "words.js"; -const EMPTY = ""; -const translate = require("./lib/tools").translateText; -const languages = { - en: {}, - de: {}, - ru: {}, - pt: {}, - nl: {}, - fr: {}, - it: {}, - es: {}, - pl: {}, - "zh-cn": {} -}; - -function lang2data(lang, isFlat) { - let str = isFlat ? "" : "{\n"; - let count = 0; - for (const w in lang) { - if (lang.hasOwnProperty(w)) { - count++; - if (isFlat) { - str += (lang[w] === "" ? (isFlat[w] || w) : lang[w]) + "\n"; - } else { - const key = ' "' + w.replace(/"/g, '\\"') + '": '; - str += key + '"' + lang[w].replace(/"/g, '\\"') + '",\n'; - } - } - } - if (!count) - return isFlat ? "" : "{\n}"; - if (isFlat) { - return str; - } else { - return str.substring(0, str.length - 2) + "\n}"; - } -} - -function readWordJs(src) { - try { - let words; - if (fs.existsSync(src + "js/" + fileName)) { - words = fs.readFileSync(src + "js/" + fileName).toString(); - } else { - words = fs.readFileSync(src + fileName).toString(); - } - words = words.substring(words.indexOf("{"), words.length); - words = words.substring(0, words.lastIndexOf(";")); - - const resultFunc = new Function("return " + words + ";"); - - return resultFunc(); - } catch (e) { - return null; - } -} - -function padRight(text, totalLength) { - return text + (text.length < totalLength ? new Array(totalLength - text.length).join(" ") : ""); -} - -function writeWordJs(data, src) { - let text = ""; - text += "/*global systemDictionary:true */\n"; - text += "'use strict';\n\n"; - text += "systemDictionary = {\n"; - for (const word in data) { - if (data.hasOwnProperty(word)) { - text += " " + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50); - let line = ""; - for (const lang in data[word]) { - if (data[word].hasOwnProperty(lang)) { - line += '"' + lang + '": "' + padRight(data[word][lang].replace(/"/g, '\\"') + '",', 50) + " "; - } - } - if (line) { - line = line.trim(); - line = line.substring(0, line.length - 1); - } - text += line + "},\n"; - } - } - text += "};"; - if (fs.existsSync(src + "js/" + fileName)) { - fs.writeFileSync(src + "js/" + fileName, text); - } else { - fs.writeFileSync(src + "" + fileName, text); - } -} - -function words2languages(src) { - const langs = Object.assign({}, languages); - const data = readWordJs(src); - if (data) { - for (const word in data) { - if (data.hasOwnProperty(word)) { - for (const lang in data[word]) { - if (data[word].hasOwnProperty(lang)) { - langs[lang][word] = data[word][lang]; - // pre-fill all other languages - for (const j in langs) { - if (langs.hasOwnProperty(j)) { - langs[j][word] = langs[j][word] || EMPTY; - } - } - } - } - } - } - if (!fs.existsSync(src + "i18n/")) { - fs.mkdirSync(src + "i18n/"); - } - for (const l in langs) { - if (!langs.hasOwnProperty(l)) - continue; - const keys = Object.keys(langs[l]); - keys.sort(); - const obj = {}; - for (let k = 0; k < keys.length; k++) { - obj[keys[k]] = langs[l][keys[k]]; - } - if (!fs.existsSync(src + "i18n/" + l)) { - fs.mkdirSync(src + "i18n/" + l); - } - - fs.writeFileSync(src + "i18n/" + l + "/translations.json", lang2data(obj)); - } - } else { - console.error("Cannot read or parse " + fileName); - } -} - -function words2languagesFlat(src) { - const langs = Object.assign({}, languages); - const data = readWordJs(src); - if (data) { - for (const word in data) { - if (data.hasOwnProperty(word)) { - for (const lang in data[word]) { - if (data[word].hasOwnProperty(lang)) { - langs[lang][word] = data[word][lang]; - // pre-fill all other languages - for (const j in langs) { - if (langs.hasOwnProperty(j)) { - langs[j][word] = langs[j][word] || EMPTY; - } - } - } - } - } - } - const keys = Object.keys(langs.en); - keys.sort(); - for (const l in langs) { - if (!langs.hasOwnProperty(l)) - continue; - const obj = {}; - for (let k = 0; k < keys.length; k++) { - obj[keys[k]] = langs[l][keys[k]]; - } - langs[l] = obj; - } - if (!fs.existsSync(src + "i18n/")) { - fs.mkdirSync(src + "i18n/"); - } - for (const ll in langs) { - if (!langs.hasOwnProperty(ll)) - continue; - if (!fs.existsSync(src + "i18n/" + ll)) { - fs.mkdirSync(src + "i18n/" + ll); - } - - fs.writeFileSync(src + "i18n/" + ll + "/flat.txt", lang2data(langs[ll], langs.en)); - } - fs.writeFileSync(src + "i18n/flat.txt", keys.join("\n")); - } else { - console.error("Cannot read or parse " + fileName); - } -} - -function languagesFlat2words(src) { - const dirs = fs.readdirSync(src + "i18n/"); - const langs = {}; - const bigOne = {}; - const order = Object.keys(languages); - dirs.sort(function (a, b) { - const posA = order.indexOf(a); - const posB = order.indexOf(b); - if (posA === -1 && posB === -1) { - if (a > b) - return 1; - if (a < b) - return -1; - return 0; - } else if (posA === -1) { - return -1; - } else if (posB === -1) { - return 1; - } else { - if (posA > posB) - return 1; - if (posA < posB) - return -1; - return 0; - } - }); - const keys = fs.readFileSync(src + "i18n/flat.txt").toString().split("\n"); - - for (const lang of dirs) { - if (lang === "flat.txt") - continue; - const values = fs.readFileSync(src + "i18n/" + lang + "/flat.txt").toString().split("\n"); - langs[lang] = {}; - keys.forEach(function (word, i) { - langs[lang][word] = values[i]; - }); - - const words = langs[lang]; - for (const word in words) { - if (words.hasOwnProperty(word)) { - bigOne[word] = bigOne[word] || {}; - if (words[word] !== EMPTY) { - bigOne[word][lang] = words[word]; - } - } - } - } - // read actual words.js - const aWords = readWordJs(); - - const temporaryIgnore = ["flat.txt"]; - if (aWords) { - // Merge words together - for (const w in aWords) { - if (aWords.hasOwnProperty(w)) { - if (!bigOne[w]) { - console.warn("Take from actual words.js: " + w); - bigOne[w] = aWords[w]; - } - dirs.forEach(function (lang) { - if (temporaryIgnore.indexOf(lang) !== -1) - return; - if (!bigOne[w][lang]) { - console.warn('Missing "' + lang + '": ' + w); - } - }); - } - } - - } - - writeWordJs(bigOne, src); -} - -function languages2words(src) { - const dirs = fs.readdirSync(src + "i18n/"); - const langs = {}; - const bigOne = {}; - const order = Object.keys(languages); - dirs.sort(function (a, b) { - const posA = order.indexOf(a); - const posB = order.indexOf(b); - if (posA === -1 && posB === -1) { - if (a > b) - return 1; - if (a < b) - return -1; - return 0; - } else if (posA === -1) { - return -1; - } else if (posB === -1) { - return 1; - } else { - if (posA > posB) - return 1; - if (posA < posB) - return -1; - return 0; - } - }); - for (const lang of dirs) { - if (lang === "flat.txt") - continue; - langs[lang] = fs.readFileSync(src + "i18n/" + lang + "/translations.json").toString(); - langs[lang] = JSON.parse(langs[lang]); - const words = langs[lang]; - for (const word in words) { - if (words.hasOwnProperty(word)) { - bigOne[word] = bigOne[word] || {}; - if (words[word] !== EMPTY) { - bigOne[word][lang] = words[word]; - } - } - } - } - // read actual words.js - const aWords = readWordJs(); - - const temporaryIgnore = ["flat.txt"]; - if (aWords) { - // Merge words together - for (const w in aWords) { - if (aWords.hasOwnProperty(w)) { - if (!bigOne[w]) { - console.warn("Take from actual words.js: " + w); - bigOne[w] = aWords[w]; - } - dirs.forEach(function (lang) { - if (temporaryIgnore.indexOf(lang) !== -1) - return; - if (!bigOne[w][lang]) { - console.warn('Missing "' + lang + '": ' + w); - } - }); - } - } - - } - - writeWordJs(bigOne, src); -} - -async function translateNotExisting(obj, baseText, yandex) { - let t = obj["en"]; - if (!t) { - t = baseText; - } - - if (t) { - for (let l in languages) { - if (!obj[l]) { - const time = new Date().getTime(); - obj[l] = await translate(t, l, yandex); - console.log("en -> " + l + " " + (new Date().getTime() - time) + " ms"); - } - } - } -} - -//TASKS - -gulp.task("adminWords2languages", function (done) { - words2languages("./admin/"); - done(); -}); - -gulp.task("adminWords2languagesFlat", function (done) { - words2languagesFlat("./admin/"); - done(); -}); - -gulp.task("adminLanguagesFlat2words", function (done) { - languagesFlat2words("./admin/"); - done(); -}); - -gulp.task("adminLanguages2words", function (done) { - languages2words("./admin/"); - done(); -}); - -gulp.task("updatePackages", function (done) { - iopackage.common.version = pkg.version; - iopackage.common.news = iopackage.common.news || {}; - if (!iopackage.common.news[pkg.version]) { - const news = iopackage.common.news; - const newNews = {}; - - newNews[pkg.version] = { - en: "news", - de: "neues", - ru: "новое", - pt: "novidades", - nl: "nieuws", - fr: "nouvelles", - it: "notizie", - es: "noticias", - pl: "nowości", - "zh-cn": "新" - }; - iopackage.common.news = Object.assign(newNews, news); - } - fs.writeFileSync("io-package.json", JSON.stringify(iopackage, null, 4)); - done(); -}); - -gulp.task("updateReadme", function (done) { - const readme = fs.readFileSync("README.md").toString(); - const pos = readme.indexOf("## Changelog\n"); - if (pos !== -1) { - const readmeStart = readme.substring(0, pos + "## Changelog\n".length); - const readmeEnd = readme.substring(pos + "## Changelog\n".length); - - if (readme.indexOf(version) === -1) { - const timestamp = new Date(); - const date = timestamp.getFullYear() + "-" + - ("0" + (timestamp.getMonth() + 1).toString(10)).slice(-2) + "-" + - ("0" + (timestamp.getDate()).toString(10)).slice(-2); - - let news = ""; - if (iopackage.common.news && iopackage.common.news[pkg.version]) { - news += "* " + iopackage.common.news[pkg.version].en; - } - - fs.writeFileSync("README.md", readmeStart + "### " + version + " (" + date + ")\n" + (news ? news + "\n\n" : "\n") + readmeEnd); - } - } - done(); -}); - -gulp.task("translate", async function (done) { - - let yandex; - const i = process.argv.indexOf("--yandex"); - if (i > -1) { - yandex = process.argv[i + 1]; - } - - if (iopackage && iopackage.common) { - if (iopackage.common.news) { - console.log("Translate News"); - for (let k in iopackage.common.news) { - console.log("News: " + k); - let nw = iopackage.common.news[k]; - await translateNotExisting(nw, null, yandex); - } - } - if (iopackage.common.titleLang) { - console.log("Translate Title"); - await translateNotExisting(iopackage.common.titleLang, iopackage.common.title, yandex); - } - if (iopackage.common.desc) { - console.log("Translate Description"); - await translateNotExisting(iopackage.common.desc, null, yandex); - } - - if (fs.existsSync("./admin/i18n/en/translations.json")) { - let enTranslations = require("./admin/i18n/en/translations.json"); - for (let l in languages) { - console.log("Translate Text: " + l); - let existing = {}; - if (fs.existsSync("./admin/i18n/" + l + "/translations.json")) { - existing = require("./admin/i18n/" + l + "/translations.json"); - } - for (let t in enTranslations) { - if (!existing[t]) { - existing[t] = await translate(enTranslations[t], l, yandex); - } - } - if (!fs.existsSync("./admin/i18n/" + l + "/")) { - fs.mkdirSync("./admin/i18n/" + l + "/"); - } - fs.writeFileSync("./admin/i18n/" + l + "/translations.json", JSON.stringify(existing, null, 4)); - } - } - - } - fs.writeFileSync("io-package.json", JSON.stringify(iopackage, null, 4)); -}); - -gulp.task("translateAndUpdateWordsJS", gulp.series("translate", "adminLanguages2words", "adminWords2languages")); - -gulp.task("default", gulp.series("updatePackages", "updateReadme")); \ No newline at end of file diff --git a/io-package.json b/io-package.json index 9431fe10..809b41d3 100644 --- a/io-package.json +++ b/io-package.json @@ -1,34 +1,98 @@ { "common": { "name": "vw-connect", - "version": "0.0.6", + "version": "0.7.15", "news": { - "0.0.6": { - "en": "add audi" + "0.7.15": { + "en": "fix vw refresh token", + "de": "vw erfrischungsgetränk", + "ru": "обновление vw rescovery token", + "pt": "corrigir o item de atualização vw", + "nl": "fix vw fresh token", + "fr": "corriger vw rafraîchir jeton", + "it": "correzione vw rinfresca token", + "es": "reparación de refresco vw", + "pl": "fix vw odświeżanie token", + "uk": "виправити vw оновлений токен", + "zh-cn": "修复 vw 刷新符号" }, - "0.0.5": { - "en": "add honk and flash, fix address format" + "0.7.14": { + "en": "fix vw id login", + "de": "vw id anmelden", + "ru": "исправить vw id login", + "pt": "corrigir login do id vw", + "nl": "vw id-login herstellen", + "fr": "correction de la connexion vw id", + "it": "correzione vw id login", + "es": "fijar vw id login", + "pl": "fix vw id logowanie", + "uk": "виправити vw id увійти", + "zh-cn": "修补 vw id 登录" }, - "0.0.4": { - "en": "add skoda support, rename adapter" + "0.7.13": { + "en": "fix for skoda login", + "de": "fix für skoda login", + "ru": "обсуждение skoda login", + "pt": "correção para o login do skoda", + "nl": "fix voor skoda login", + "fr": "correction pour la connexion skoda", + "it": "correzione per skoda login", + "es": "fix for skoda login", + "pl": "fix for skoda logowanie", + "uk": "виправлення для входу в скоду", + "zh-cn": "用于 skoda 登录的修复" }, - "0.0.3": { - "en": "add more information, add remote controls" + "0.7.12": { + "en": "fix for skoda refresh token and ventilation activation and add new ignored endpoints", + "de": "fix für skoda refresh token und Lüftungsaktivierung und neue ignorierte Endpunkte hinzufügen", + "ru": "обновление skoda refresh toke", + "pt": "corrigir para skoda refrescar toke", + "nl": "fix voor skoda refresh toke", + "fr": "fix pour skoda rafraîchissement toke", + "it": "correzione per skoda rinfrescare toke", + "es": "fijado para skoda refresco toke", + "pl": "fix for skoda refresh toke", + "uk": "закріпити для skoda освіжаючий токе", + "zh-cn": "用于 skoda 刷新脚趾" }, - "0.0.2": { - "en": "add car status capturing" + "0.7.9": { + "en": "fix for id wall charger", + "de": "Fix für id wall charger", + "ru": "зарядное устройство id wall", + "pt": "correção para carregador de parede id", + "nl": "fix voor id wandlader", + "fr": "fixation pour chargeur mural id", + "it": "fissaggio per caricatore da parete id", + "es": "fijación para el cargador de pared id", + "pl": "fix for id wall charger", + "uk": "фіксатор для зарядного пристрою для стін", + "zh-cn": "id 墙壁充电器的修复" }, - "0.0.1": { - "en": "initial release", - "de": "Erstveröffentlichung", - "ru": "Начальная версия", - "pt": "lançamento inicial", - "nl": "Eerste uitgave", - "fr": "Première version", - "it": "Versione iniziale", - "es": "Versión inicial", - "pl": "Pierwsze wydanie", - "zh-cn": "首次出版" + "0.7.7": { + "en": "fix for skoda auxiliaryheating and duration\nfix for skoda lock/unlock", + "de": "Fix für Skoda Standheizung und Dauer\nFix für Skoda Verriegeln/Entriegeln", + "ru": "фиксация для skoda вспомогательного нагрева и продолжительности\nобсуждение skoda lock/unlock", + "pt": "correção para auxiliar de skoda e duração\ncorreção para skoda bloqueio / desbloqueio", + "nl": "fix voor skoda hulpverwarming en duur\nfix voor skoda slot/ontgrendeling", + "fr": "fix pour le chauffage auxiliaire de skoda et la durée\ncorrection pour verrouillage/déverrouillage skoda", + "it": "fissaggio per skoda ausiliare riscaldamento e durata\nfissaggio per blocco skoda / sbloccaggio", + "es": "fijación para skoda auxiliar y duración\nskoda lock/unlock", + "pl": "fix for skoda expliliaryheration and timing\nfix for skoda lock / odblokuj", + "uk": "фіксація для допоміжного підігріву та тривалості\nфіксація для замка скода/розблокування", + "zh-cn": "斯柯达辅助加热和持续时间\nskoda 锁/ 解锁的固定" + }, + "0.7.6": { + "en": "fix for charging status updates only at startup\nfix for skoda ismoving state", + "de": "Fix für charging.status aktualisierungen nur beim Start\nFix für Skoda ismoving state", + "ru": "исправление для зарядки обновлений статуса только при запуске\nобсуждение skoda ismoving state", + "pt": "corrigir para carregar atualizações de status apenas na inicialização\ncorreção para skoda ismoving estado", + "nl": "fix voor het opladen status updates alleen bij opstarten\nfix voor skoda verplaatst toestand", + "fr": "correction pour les mises à jour d'état de chargement uniquement au démarrage\ncorrection pour l'état de déplacement de skoda", + "it": "correzione per la ricarica degli aggiornamenti di stato solo all'avvio\ncorrezione per skoda ismoving stato", + "es": "fijado para actualizaciones de estado de carga sólo al inicio\nskoda ismoving state", + "pl": "ustawienie do ładowania aktualizacji statusu tylko przy starcie\nfix for skoda ismoving state", + "uk": "виправити оновлення стану зарядки тільки при запуску\nфіксація для skoda ізмовляючого стану", + "zh-cn": "仅在启动时更新充电状态的固定\n斯柯达状态的修复" } }, "title": "VW Connect", @@ -57,7 +121,8 @@ "zh-cn": "大众我们连接的适配器" }, "authors": [ - "ta2k " + "ta2k ", + "Sneak-L8 " ], "keywords": [ "VW", @@ -66,7 +131,11 @@ "myAudi", "Skoda", "Skoda Connect", - "VW Connect" + "VW Connect", + "VW Connect ID", + "ID.3", + "VW Connect Go", + "Seat" ], "license": "MIT", "platform": "Javascript/Node.js", @@ -77,16 +146,64 @@ "readme": "https://github.com/ta2k/ioBroker.vw-connect/blob/master/README.md", "loglevel": "info", "mode": "daemon", - "type": "household", + "type": "vehicle", "compact": true, + "connectionType": "cloud", + "dataSource": "poll", "materialize": true, - "supportCustoms": true, + "plugins": { + "sentry": { + "dsn": "https://8318f3635f334042a0525ee37a87a587@sentry.iobroker.net/66" + } + }, "dependencies": [ { "js-controller": ">=1.4.2" } ] }, + "messages": [ + { + "condition": { + "operand": "and", + "rules": [ + "oldVersion<0.7.0", + "newVersion>=0.7.0" + ] + }, + "title": { + "en": "Adapter Change for Skoda and Seat", + "de": "Adapter Änderung für Skoda und Seat", + "ru": "Изменение адаптера для Skoda и Seat", + "pt": "Alteração do adaptador para Skoda e Seat", + "nl": "Adapterwijziging voor Skoda en Seat", + "fr": "Changement d'adaptateur pour Skoda et Seat", + "it": "Cambiamento dell'adattatore per Skoda e Seat", + "es": "Cambio de adaptador para Skoda y Seat", + "pl": "Zmiana adaptera dla Skoda i Seat", + "uk": "Зміна адаптера для Skoda та Seat", + "zh-cn": "Skoda 和 Seat 的适配器更改" + }, + "text": { + "en": "The adapter has completly changed with for Skoda and Seat. Please delete old states and adapt your vis and scripts to the new states.", + "de": "Der Adapter hat sich komplett geändert für Skoda und Seat. Bitte löschen Sie alte States und passen Sie Ihre Vis und Skripte an die neuen States an.", + "ru": "Адаптер полностью изменился для Skoda и Seat. Пожалуйста, удалите старые состояния и адаптируйте свои визуализации и скрипты к новым состояниям.", + "pt": "O adaptador mudou completamente para Skoda e Seat. Por favor, exclua os estados antigos e adapte sua vis e scripts aos novos estados.", + "nl": "De adapter is volledig veranderd voor Skoda en Seat. Verwijder alstublieft oude states en pas uw vis en scripts aan op de nieuwe states.", + "fr": "L'adaptateur a complètement changé pour Skoda et Seat. Veuillez supprimer les anciens états et adapter votre vis et vos scripts aux nouveaux états.", + "it": "L'adattatore è completamente cambiato per Skoda e Seat. Si prega di eliminare i vecchi stati e adattare la propria vis e gli script ai nuovi stati.", + "es": "El adaptador ha cambiado completamente para Skoda y Seat. Por favor, elimine los estados antiguos y adapte su vis y scripts a los nuevos estados.", + "pl": "Adapter całkowicie zmienił się dla Skoda i Seat. Proszę usunąć stare stany i dostosować swoje wizy i skrypty do nowych stanów.", + "uk": "Адаптер повністю змінився для Skoda та Seat. Будь ласка, видаліть старі стани та адаптуйте свої візуалізації та скрипти до нових станів.", + "zh-cn": "适配器已完全更改为 Skoda 和 Seat。请删除旧状态,并将您的 vis 和脚本调整为新状态。" + }, + "level": "warn", + "buttons": [ + "agree", + "cancel" + ] + } + ], "native": { "user": "", "password": "", @@ -94,13 +211,24 @@ "rtoken": "", "vwatoken": "", "vwrtoken": "", + "wc_access_token": "", + "wc_refresh_token": "", "userid": "", + "pin": "", "identifier": "", "interval": 15, + "forceinterval": 360, + "historyLimit": 100, "type": "vw", "reversePos": false, "rights": false, - "tripType": "shortTerm" + "tripType": "none", + "numberOfTrips": 1, + "lastTrips": true, + "lastTripDays": 30, + "tripShortTerm": false, + "tripLongTerm": false, + "tripCyclic": false }, "objects": [], "instanceObjects": [ @@ -126,4 +254,4 @@ "native": {} } ] -} \ No newline at end of file +} diff --git a/iob_npm.done b/iob_npm.done deleted file mode 100644 index 0519ecba..00000000 --- a/iob_npm.done +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lib/extractKeys.js b/lib/extractKeys.js new file mode 100644 index 00000000..95ee9bb1 --- /dev/null +++ b/lib/extractKeys.js @@ -0,0 +1,197 @@ +//v2.5 +async function extractKeys(adapter, path, element, preferedArrayName, forceIndex) { + try { + if (element === null || element === undefined) { + adapter.log.debug("Cannot extract empty: " + path); + return; + } + const objectKeys = Object.keys(element); + let write = false; + if (Array.isArray(element)) { + extractArray(adapter, element, "", path, write, preferedArrayName, forceIndex); + return; + } + if (path.toLowerCase().endsWith("settings")) { + await adapter.setObjectNotExistsAsync(path, { + type: "state", + common: { + name: "Einstellungen sind hier änderbar / You can change the settings here", + role: "indicator", + write: false, + read: true, + }, + native: {}, + }); + write = true; + } + if (typeof element === "string" || typeof element === "number") { + let name = element; + if (typeof element === "number") { + name = element.toString(); + } + adapter + .setObjectNotExistsAsync(path, { + type: "state", + common: { + name: name, + role: "indicator", + type: typeof element, + write: write, + read: true, + }, + native: {}, + }) + .then(() => { + adapter.setState(path, element, true); + }) + .catch((error) => { + adapter.log.error(error); + }); + return; + } + + objectKeys.forEach((key) => { + if (Array.isArray(element[key])) { + extractArray(adapter, element, key, path, write, preferedArrayName, forceIndex); + } else if (element[key] !== null && typeof element[key] === "object") { + extractKeys(adapter, path + "." + key, element[key], preferedArrayName, forceIndex); + } else { + if (path.indexOf("stations.") > -1 && path.indexOf("sessions.") > -1 && key === "id") { + adapter.setObjectNotExistsAsync(path + ".stopCharging", { + type: "state", + common: { + name: "Stoppe Ladung", + type: "boolean", + role: "boolean", + write: true, + }, + native: {}, + }); + } + if (path.indexOf("stations.") > -1 && path.split(".").length === 4 && key === "id") { + adapter.setObjectNotExistsAsync(path + ".startCharging", { + type: "state", + common: { + name: "Starte Ladung", + type: "boolean", + role: "boolean", + write: true, + }, + native: {}, + }); + } + + adapter + .setObjectNotExistsAsync(path + "." + key, { + type: "state", + common: { + name: key, + role: "indicator", + type: typeof element[key], + write: write, + read: true, + }, + native: {}, + }) + .then(() => { + adapter.setState(path + "." + key, element[key], true); + }) + .catch((error) => { + adapter.log.error(error); + }); + } + }); + } catch (error) { + adapter.log.error("Error extract keys: " + path + " " + JSON.stringify(element)); + adapter.log.error(error); + } +} +function extractArray(adapter, element, key, path, write, preferedArrayName, forceIndex) { + try { + if (key) { + element = element[key]; + } + element.forEach(async (arrayElement, index) => { + index = index + 1; + if (index < 10) { + index = "0" + index; + } + let arrayPath = key + index; + + if (typeof arrayElement[Object.keys(arrayElement)[0]] === "string") { + arrayPath = arrayElement[Object.keys(arrayElement)[0]]; + } + Object.keys(arrayElement).forEach((keyName) => { + if (keyName.endsWith("Id")) { + if (arrayElement[keyName].replace) { + arrayPath = arrayElement[keyName].replace(/\./g, ""); + } else { + arrayPath = arrayElement[keyName]; + } + } + }); + Object.keys(arrayElement).forEach((keyName) => { + if (keyName.endsWith("Name")) { + arrayPath = arrayElement[keyName]; + } + }); + + if (arrayElement.id) { + if (arrayElement.id.replace) { + arrayPath = arrayElement.id.replace(/\./g, ""); + } else { + arrayPath = arrayElement.id; + } + } + if (arrayElement.name) { + arrayPath = arrayElement.name.replace(/\./g, ""); + } + if (arrayElement.start_date_time) { + arrayPath = arrayElement.start_date_time.replace(/\./g, ""); + } + if (preferedArrayName && arrayElement[preferedArrayName]) { + arrayPath = arrayElement[preferedArrayName].replace(/\./g, ""); + } + + if (forceIndex) { + arrayPath = key + index; + } + //special case array with 2 string objects + if ( + Object.keys(arrayElement).length === 2 && + typeof Object.keys(arrayElement)[0] === "string" && + typeof Object.keys(arrayElement)[1] === "string" && + typeof arrayElement[Object.keys(arrayElement)[0]] !== "object" && + typeof arrayElement[Object.keys(arrayElement)[1]] !== "object" + ) { + let subKey = arrayElement[Object.keys(arrayElement)[0]]; + const subValue = arrayElement[Object.keys(arrayElement)[1]]; + const subName = Object.keys(arrayElement)[0] + " " + Object.keys(arrayElement)[1]; + if (key) { + subKey = key + "." + subKey; + } + await adapter.setObjectNotExistsAsync(path + "." + subKey, { + type: "state", + common: { + name: subName, + role: "indicator", + type: typeof subValue, + write: write, + read: true, + }, + native: {}, + }); + adapter.setState(path + "." + subKey, subValue, true); + return; + } + extractKeys(adapter, path + "." + arrayPath, arrayElement, preferedArrayName, forceIndex); + }); + } catch (error) { + adapter.log.error("Cannot extract array " + path); + adapter.log.error(error); + } +} + +module.exports = { + extractKeys, +}; diff --git a/lib/tools.js b/lib/tools.js index 8df52135..c12f1498 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -6,11 +6,11 @@ const axios = require("axios"); * @returns {it is Record} */ function isObject(it) { - // This is necessary because: - // typeof null === 'object' - // typeof [] === 'object' - // [] instanceof Object === true - return Object.prototype.toString.call(it) === "[object Object]"; + // This is necessary because: + // typeof null === 'object' + // typeof [] === 'object' + // [] instanceof Object === true + return Object.prototype.toString.call(it) === "[object Object]"; } /** @@ -19,8 +19,8 @@ function isObject(it) { * @returns {it is any[]} */ function isArray(it) { - if (typeof Array.isArray === "function") return Array.isArray(it); - return Object.prototype.toString.call(it) === "[object Array]"; + if (typeof Array.isArray === "function") return Array.isArray(it); + return Object.prototype.toString.call(it) === "[object Array]"; } /** @@ -31,14 +31,14 @@ function isArray(it) { * @returns {Promise} */ async function translateText(text, targetLang, yandexApiKey) { - if (targetLang === "en") { - return text; - } - if (yandexApiKey) { - return await translateYandex(text, targetLang, yandexApiKey); - } else { - return await translateGoogle(text, targetLang); - } + if (targetLang === "en") { + return text; + } + if (yandexApiKey) { + return await translateYandex(text, targetLang, yandexApiKey); + } else { + return await translateGoogle(text, targetLang); + } } /** @@ -49,19 +49,19 @@ async function translateText(text, targetLang, yandexApiKey) { * @returns {Promise} */ async function translateYandex(text, targetLang, apiKey) { - if (targetLang === "zh-cn") { - targetLang = "zh"; - } - try { - const url = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${apiKey}&text=${encodeURIComponent(text)}&lang=en-${targetLang}`; - const response = await axios({url, timeout: 15000}); - if (response.data && response.data["text"]) { - return response.data["text"][0]; - } - throw new Error("Invalid response for translate request"); - } catch (e) { - throw new Error(`Could not translate to "${targetLang}": ${e}`); - } + if (targetLang === "zh-cn") { + targetLang = "zh"; + } + try { + const url = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${apiKey}&text=${encodeURIComponent(text)}&lang=en-${targetLang}`; + const response = await axios({url, timeout: 15000}); + if (response.data && response.data["text"]) { + return response.data["text"][0]; + } + throw new Error("Invalid response for translate request"); + } catch (e) { + throw new Error(`Could not translate to "${targetLang}": ${e}`); + } } /** @@ -71,21 +71,21 @@ async function translateYandex(text, targetLang, apiKey) { * @returns {Promise} */ async function translateGoogle(text, targetLang) { - try { - const url = `http://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}&ie=UTF-8&oe=UTF-8`; - const response = await axios({url, timeout: 15000}); - if (isArray(response.data)) { - // we got a valid response - return response.data[0][0][0]; - } - throw new Error("Invalid response for translate request"); - } catch (e) { - throw new Error(`Could not translate to "${targetLang}": ${e}`); - } + try { + const url = `http://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}&ie=UTF-8&oe=UTF-8`; + const response = await axios({url, timeout: 15000}); + if (isArray(response.data)) { + // we got a valid response + return response.data[0][0][0]; + } + throw new Error("Invalid response for translate request"); + } catch (e) { + throw new Error(`Could not translate to "${targetLang}": ${e}`); + } } module.exports = { - isArray, - isObject, - translateText + isArray, + isObject, + translateText }; diff --git a/main.js b/main.js index 45ce6bc9..2907182c 100644 --- a/main.js +++ b/main.js @@ -1,3 +1,4 @@ +// @ts-nocheck "use strict"; /* @@ -8,1330 +9,7018 @@ // you need to create an adapter const utils = require("@iobroker/adapter-core"); - const request = require("request"); +const qs = require("qs"); const crypto = require("crypto"); -const uuidv4 = require("uuid/v4"); +const { Crypto } = require("@peculiar/webcrypto"); +const { v4: uuidv4 } = require("uuid"); const traverse = require("traverse"); -const jsdom = require("jsdom"); -const { - JSDOM -} = jsdom; +const geohash = require("ngeohash"); +const { extractKeys } = require("./lib/extractKeys"); +const axios = require("axios").default; +const Json2iob = require("json2iob"); +const mqtt = require("mqtt"); +const uuid = require("uuid"); class VwWeconnect extends utils.Adapter { + /** + * @param {Partial} [options={}] + */ + constructor(options) { + super({ + ...options, + name: "vw-connect", + }); + this.on("ready", this.onReady.bind(this)); + // this.on("objectChange", this.onObjectChange.bind(this)); + this.on("stateChange", this.onStateChange.bind(this)); + // this.on("message", this.onMessage.bind(this)); + this.on("unload", this.onUnload.bind(this)); + this.extractKeys = extractKeys; + + this.json2iob = new Json2iob(this); + this.jar = request.jar(); + this.userAgent = "iobroker v"; + this.skodaUserAgent = "MySkoda/Android/8.0.0/250203003"; + this.androidPackageName = "com.volkswagen.weconnect"; + this.refreshTokenInterval = null; + this.vwrefreshTokenInterval = null; + this.updateInterval = null; + this.fupdateInterval = null; + this.refreshTokenTimeout = null; + + this.homeRegion = {}; + this.homeRegionSetter = {}; + this.secondAccessToken = null; + + this.ignoredPaths = {}; + this.vinArray = []; + this.etags = {}; + this.hasRemoteLock = false; + this.isFirstLocation = true; + this.lastTripCheck = 0; + this.firstStart = true; + this.blockTrip = {}; + + this.statesArray = [ + { + url: "$homeregion/fs-car/bs/departuretimer/v1/$type/$country/vehicles/$vin/timer", + path: "timer", + element: "timer", + }, + { + url: "$homeregion/fs-car/bs/climatisation/v1/$type/$country/vehicles/$vin/climater", + path: "climater", + element: "climater", + }, + { + url: "$homeregion/fs-car/bs/cf/v1/$type/$country/vehicles/$vin/position", + path: "position", + element: "storedPositionResponse", + element2: "position", + element3: "findCarResponse", + element4: "Position", + }, + { + url: "$homeregion/fs-car/bs/tripstatistics/v1/$type/$country/vehicles/$vin/tripdata/$tripType?type=list", + path: "tripdata", + element: "tripDataList", + }, + { + url: "$homeregion/fs-car/bs/vsr/v1/$type/$country/vehicles/$vin/status", + path: "status", + element: "StoredVehicleDataResponse", + element2: "vehicleData", + }, + { + url: "$homeregion/fs-car/bs/batterycharge/v1/$type/$country/vehicles/$vin/charger", + path: "charger", + element: "charger", + }, + { + url: "$homeregion/fs-car/bs/rs/v1/$type/$country/vehicles/$vin/status", + path: "remoteStandheizung", + element: "statusResponse", + }, + { + url: "$homeregion/fs-car/bs/dwap/v1/$type/$country/vehicles/$vin/history", + path: "history", + }, + ]; + } + + /** + * Is called when databases are connected and adapter received configuration. + */ + async onReady() { + // Initialize your adapter here + + this.setState("info.connection", false, true); + if (!this.config.password) { + this.log.warn("Please enter password"); + return; + } + this.userAgent += this.version; + // Reset the connection indicator during startup + this.type = "VW"; + this.country = "DE"; + this.clientId = "9496332b-ea03-4091-a224-8c746b885068%40apps_vw-dilab_com"; + this.xclientId = "38761134-34d0-41f3-9a73-c4be88d7d337"; + this.scope = "openid%20profile%20mbb%20email%20cars%20birthdate%20badge%20address%20vin"; + this.redirect = "carnet%3A%2F%2Fidentity-kit%2Flogin"; + this.xrequest = "de.volkswagen.carnet.eu.eremote"; + this.responseType = "id_token%20token%20code"; + this.xappversion = "5.1.2"; + this.xappname = "eRemote"; + if (this.config.type === "vw") { + this.log.info("WeConnect App is disabled switch to ID/Volkswagen App"); + this.config.type = "id"; + } + if (this.config.type === "skoda") { + this.type = "Skoda"; + this.country = "CZ"; + this.clientId = "f9a2359a-b776-46d9-bd0c-db1904343117@apps_vw-dilab_com"; + this.xclientId = "afb0473b-6d82-42b8-bfea-cead338c46ef"; + this.scope = "openid mbb profile"; + this.redirect = "skodaconnect://oidc.login/"; + this.xrequest = "cz.skodaauto.connect"; + this.responseType = "code%20id_token"; + this.xappversion = "3.2.6"; + this.xappname = "cz.skodaauto.connect"; + } + if (this.config.type === "skodae") { + this.type = "Skoda"; + this.country = "CZ"; + this.clientId = "7f045eee-7003-4379-9968-9355ed2adb06@apps_vw-dilab_com"; + this.xclientId = "afb0473b-6d82-42b8-bfea-cead338c46ef"; + this.scope = + "address badge birthdate cars driversLicense dealers email mileage mbb nationalIdentifier openid phone profession profile vin"; + this.redirect = "myskoda%3A%2F%2Fredirect%2Flogin%2F"; + this.xrequest = "cz.skodaauto.connect"; + this.responseType = "code"; + this.xappversion = "8.0.0"; + this.xappname = "cz.skodaauto.connect"; + this.xbrand = "skoda"; + } + if (this.config.type === "seat") { + this.type = "Seat"; + this.country = "ES"; + this.clientId = "3c8e98bc-3ae9-4277-a563-d5ee65ddebba@apps_vw-dilab_com"; + this.xclientId = "9dcc70f0-8e79-423a-a3fa-4065d99088b4"; + this.scope = "openid profile address phone email birthdate nationalIdentifier cars mbb dealers badge nationality"; + this.redirect = "seatconnect://identity-kit/login"; + // this.xrequest = "cz.skodaauto.connect"; + this.responseType = "code%20id_token%20token"; + this.xappversion = "1.11.2"; + this.xappname = "SEAT Connect"; + } + if (this.config.type === "seatcupra") { + this.type = "Seat"; + // this.clientId = "3c756d46-f1ba-4d78-9f9a-cff0d5292d51@apps_vw-dilab_com"; + this.clientId = "99a5b77d-bd88-4d53-b4e5-a539c60694a3@apps_vw-dilab_com"; + this.scope = "openid profile nickname birthdate phone"; + // this.redirect = "cupra://oauth-callback"; + this.redirect = "seat://oauth-callback"; + this.responseType = "code"; + this.xappversion = "1.1.29"; + this.xappname = "SEATConnect"; + } + if (this.config.type === "seatcupra2") { + this.type = "Seat"; + this.clientId = "30e33736-c537-4c72-ab60-74a7b92cfe83@apps_vw-dilab_com"; + this.scope = "openid profile address phone email birthdate nationalIdentifier cars mbb dealers badge nationality"; + this.redirect = "cupraconnect://identity-kit/login"; + this.responseType = "code id_token token"; + this.xappversion = "1.1.29"; + + this.xclientId = "9d183b70-d129-424f-9a26-c3778edf95e1"; + this.xappname = "SEATConnect"; + } + if (this.config.type === "vwv2") { + this.log.info("WeConnect App is disabled switch to ID/Volkswagen App"); + this.config.type = "id"; + this.type = "VW"; + this.country = "DE"; + this.clientId = "9496332b-ea03-4091-a224-8c746b885068@apps_vw-dilab_com"; + this.xclientId = "89312f5d-b853-4965-a471-b0859ee468af"; + this.scope = "openid profile mbb cars birthdate nickname address phone"; + this.redirect = "carnet://identity-kit/login"; + this.xrequest = "de.volkswagen.car-net.eu.e-remote"; + this.responseType = "id_token%20token%20code"; + this.xappversion = "5.6.7"; + this.xappname = "We Connect"; + } + if (this.config.type === "id") { + this.type = "Id"; + this.country = "DE"; + this.clientId = "a24fba63-34b3-4d43-b181-942111e6bda8@apps_vw-dilab_com"; + this.xclientId = ""; + this.scope = "openid profile badge cars dealers birthdate vin"; + this.redirect = "weconnect://authenticated"; + this.xrequest = "com.volkswagen.weconnect"; + this.responseType = "code id_token token"; + this.xappversion = "3.51.1"; + this.xappname = "Volkswagen"; + this.xbrand = "volkswagen"; + this.userAgent = "Volkswagen/3.51.1-android/14"; + } + if (this.config.type === "audi") { + this.log.info("Login in with audi as audietron"); + this.config.type = "audietron"; + // this.type = "Audi"; + // this.country = "DE"; + // this.clientId = "09b6cbec-cd19-4589-82fd-363dfa8c24da@apps_vw-dilab_com"; + // this.xclientId = "77869e21-e30a-4a92-b016-48ab7d3db1d8"; + // this.scope = + // "address profile badge birthdate birthplace nationalIdentifier nationality profession email vin phone nickname name picture mbb gallery openid"; + // this.redirect = "myaudi:///"; + // this.xrequest = "de.myaudi.mobile.assistant"; + // this.responseType = "token%20id_token"; + // // this.responseType = "code"; + // this.xappversion = "3.22.0"; + // this.xappname = "myAudi"; + } + if (this.config.type === "audietron") { + this.type = "Audi"; + this.country = "DE"; + this.clientId = "f4d0934f-32bf-4ce4-b3c4-699a7049ad26@apps_vw-dilab_com"; + this.scope = + "address badge birthdate birthplace email gallery mbb name nationalIdentifier nationality nickname phone picture profession profile vin openid"; + this.redirect = "myaudi:///"; + this.responseType = "code"; + this.xappversion = "4.14.1"; + this.xappname = "myAudi"; + this.xclientId = "59edf286-a9ca-4d34-9421-68da00f72dc8"; + } + if (this.config.type === "audidata") { + this.type = "Audi"; + this.country = "DE"; + this.clientId = "ec6198b1-b31e-41ec-9a69-95d42d6497ed@apps_vw-dilab_com"; + this.scope = "openid profile address email phone"; + this.redirect = "acpp://de.audi.connectplugandplay/oauth2redirect/identitykit"; + this.responseType = "code"; + } + if (this.config.type === "go") { + this.type = ""; + this.country = ""; + this.clientId = "ac42b0fa-3b11-48a0-a941-43a399e7ef84@apps_vw-dilab_com"; + this.xclientId = ""; + this.scope = "openid%20profile%20address%20email%20phone"; + this.redirect = "vwconnect%3A%2F%2Fde.volkswagen.vwconnect%2Foauth2redirect%2Fidentitykit"; + this.xrequest = ""; + this.responseType = "code"; + this.xappversion = ""; + this.xappname = ""; + } + if (this.config.type === "seatelli") { + this.type = ""; + this.country = ""; + this.clientId = "d940d794-5945-48a3-84b1-44222c387800@apps_vw-dilab_com"; + this.xclientId = ""; + this.scope = "openid profile"; + this.redirect = "Seat-elli-hub://opid"; + this.xrequest = ""; + this.responseType = "code"; + this.xappversion = ""; + this.xappname = ""; + } + if (this.config.type === "skodapower") { + this.type = ""; + this.country = ""; + this.clientId = "b84ba8a1-7925-43c9-9963-022587faaac5@apps_vw-dilab_com"; + this.xclientId = ""; + this.scope = "openid profile"; + this.redirect = "skoda-hub://opid"; + this.xrequest = ""; + this.responseType = "code"; + this.xappversion = ""; + this.xappname = ""; + } + if (!this.config.interval || this.config.interval < 0.5) { + this.log.info("Interval of 0 is not allowed reset to 1"); + this.config.interval = 1; + } + // if (this.config.type === "skodae") { + // // this.log.info("Parking Postion is temporary disabled for Skoda E"); + // if (this.config.interval < 10) { + // this.log.info("Interval under 10min is temporary not allowed for Skoda E reset to 10min"); + // this.config.interval = 10; + // } + // } + + this.tripTypes = []; + if (this.config.tripShortTerm == true) { + this.tripTypes.push("shortTerm"); + } + if (this.config.tripLongTerm == true) { + this.tripTypes.push("longTerm"); + } + if (this.config.tripCyclic == true) { + this.tripTypes.push("cyclic"); + } + + this.login() + .then(() => { + this.log.info("Login successful"); + this.setState("info.connection", true, true); + this.extendObject("refresh", { + type: "state", + common: { + name: "Refresh All States", + type: "boolean", + role: "button", + write: true, + }, + native: {}, + }); + this.getPersonalData().then(() => { + this.getVehicles() + .then(() => { + if (this.config.type !== "go") { + this.vinArray.forEach((vin) => { + if (this.config.type === "id" || this.config.type === "audietron") { + this.getHomeRegion(vin); + + this.getIdStatus(vin).catch(() => { + this.log.error("get id status Failed"); + }); + } else if (this.config.type === "seatcupra") { + this.getSeatCupraStatus(vin); + } else if (this.config.type === "audidata") { + this.getAudiDataStatus(vin).catch(() => { + this.log.error("get audi data status Failed"); + }); + } else if (this.config.type === "skodae") { + this.getSkodaEStatus(vin); + } else { + this.getHomeRegion(vin) + .catch(() => { + this.log.debug("get home region Failed " + vin); + }) + .finally(() => { + this.getVehicleData(vin).catch(() => { + this.log.error("get vehicle data Failed"); + }); + this.getVehicleRights(vin).catch(() => { + this.log.error("get vehicle rights Failed"); + }); + this.requestStatusUpdate(vin) + .finally(() => { + this.statesArray.forEach((state) => { + if (state.path == "tripdata") { + this.tripTypes.forEach((tripType) => { + this.getVehicleStatus( + vin, + state.url, + state.path, + state.element, + state.element2, + state.element3, + state.element4, + tripType, + ).catch(() => { + this.log.debug("error while getting " + state.url); + }); + }); + } else { + this.getVehicleStatus( + vin, + state.url, + state.path, + state.element, + state.element2, + state.element3, + state.element4, + ).catch(() => { + this.log.debug("error while getting " + state.url); + }); + } + }); + }) + .catch(() => { + this.log.error("status update Failed " + vin); + }); + }) + .catch(() => { + this.log.error("Error getting home region"); + }); + } + }); + } + if (this.config.type !== "skodae" && this.config.type !== "seatcupra") { + this.updateStatus(); + } + this.updateInterval && clearInterval(this.updateInterval); + this.updateInterval = setInterval(() => { + this.updateStatus(); + }, this.config.interval * 60 * 1000); + + if (this.config.type !== "id" && this.config.type !== "skodae" && this.config.type !== "audietron") { + if (this.config.forceinterval > 0) { + this.fupdateInterval = setInterval(() => { + if (this.config.type === "go") { + this.getVehicles(); + return; + } + this.vinArray.forEach((vin) => { + this.requestStatusUpdate(vin).catch(() => { + this.log.error("force status update Failed"); + }); + }); + }, this.config.forceinterval * 60 * 1000); + } + } + + if (this.config.type === "seatelli" || this.config.type === "skodapower") { + this.getElliData(this.config.type).catch(() => { + this.log.error("get elli Failed"); + }); + } + }) + .catch(() => { + this.log.error("Get Vehicles Failed"); + }); + }); + }) + .catch(() => { + this.log.error("Login Failed"); + this.log.error("Restart Adapter in 30min"); + setTimeout(() => { + this.log.error("Restart adapter"); + this.restart(); + }, 30 * 60 * 1000); + }); + this.subscribeStates("*"); + } + + login() { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + const nonce = this.getNonce(); + const state = uuidv4(); + this.log.info(`Login in with ${this.config.type}`); + let [code_verifier, codeChallenge] = this.getCodeChallenge(); + if (this.config.type === "seatelli" || this.config.type === "skodapower") { + [code_verifier, codeChallenge] = this.getCodeChallengev2(); + } + const method = "GET"; + const form = {}; + let url = + "https://identity.vwgroup.io/oidc/v1/authorize?client_id=" + + this.clientId + + "&scope=" + + this.scope + + "&response_type=" + + this.responseType + + "&redirect_uri=" + + this.redirect + + "&nonce=" + + nonce + + "&state=" + + state; + if ( + this.config.type === "vw" || + this.config.type === "vwv2" || + this.config.type === "go" || + this.config.type === "seatelli" || + this.config.type === "skodae" || + this.config.type === "skodapower" || + this.config.type === "audidata" || + this.config.type === "audietron" || + this.config.type === "seatcupra" || + this.config.type === "seatcupra2" + ) { + url += "&code_challenge=" + codeChallenge + "&code_challenge_method=S256"; + } + if (this.config.type === "audi") { + url += "&ui_locales=de-DE%20de&prompt=login"; + } + if (this.config.type === "id" && this.type !== "Wc") { + // For ID type: Use direct OpenID authorization endpoint (Python-style) + // This ensures the code is compatible with the OpenID token endpoint + this.log.debug("Using direct OpenID authorization endpoint (Python-style, no code_challenge)"); + // The standard url above is already correct - it has all the right params + // Just make sure we're NOT adding code_challenge for ID type + } + this.log.debug("Login URL: " + url); + const loginRequest = request( + { + method: method, + url: url, + headers: { + "User-Agent": this.userAgent, + Accept: + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", + "Accept-Language": "en-US,en;q=0.9", + "Accept-Encoding": "gzip, deflate", + "x-requested-with": this.xrequest, + "upgrade-insecure-requests": 1, + }, + jar: this.jar, + form: form, + gzip: true, + followAllRedirects: true, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + if (this.type === "Wc") { + if (err && err.message && err.message === "Invalid protocol: wecharge:") { + this.log.debug("Found WeCharge connection"); + this.getTokens(loginRequest, code_verifier, reject, resolve); + } else { + this.log.debug("No WeCharge found, cancel login"); + resolve(); + } + return; + } + if (err && err.message && err.message.indexOf("Invalid protocol:") !== -1) { + this.log.debug("Found Token"); + this.getTokens(loginRequest, code_verifier, reject, resolve); + return; + } + this.log.error("Failed in first login step "); + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + err && err.message && this.log.error(err.message); + loginRequest && + loginRequest.uri && + loginRequest.uri.query && + this.log.debug(loginRequest.uri.query.toString()); + + reject(); + return; + } + + try { + const stateToken = this.extractStateToken(body); + + // New authentication flow with state token + if (stateToken) { + this.log.info("Using new authentication flow with state token"); + const loginForm = { + username: this.config.user, + password: this.config.password, + state: stateToken + }; + + const loginHeaders = { + "Content-Type": "application/x-www-form-urlencoded", + "User-Agent": this.userAgent, + Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", + "Accept-Language": "en-US,en;q=0.9", + "Accept-Encoding": "gzip, deflate", + "x-requested-with": this.xrequest, + }; + + if (this.config.type === "id" && this.androidPackageName) { + loginHeaders["x-android-package-name"] = this.androidPackageName; + } + + request.post( + { + url: "https://identity.vwgroup.io/u/login?state=" + stateToken, + headers: loginHeaders, + form: loginForm, + jar: this.jar, + gzip: true, + followAllRedirects: false, // Follow redirects manually + }, + (err, resp, _body) => { + if (err || (resp && resp.statusCode >= 400)) { + this.log.error("Failed new authentication flow"); + err && this.log.error(err.message); + resp && this.log.error("Status: " + resp.statusCode); + reject(); + return; + } + + try { + // Follow redirects manually like Python does + if (!resp.headers.location) { + this.log.error("No redirect location in response"); + reject(); + return; + } + + let redirectUrl = resp.headers.location; + if (redirectUrl.startsWith("/")) { + redirectUrl = "https://identity.vwgroup.io" + redirectUrl; + } + + this.log.debug("Starting manual redirect following from: " + redirectUrl.substring(0, 100)); + + // Follow redirects manually until we hit weconnect:// + this.followRedirectsManually(redirectUrl, 0, code_verifier, reject, resolve); + } catch (err) { + this.log.error("Error processing new auth response"); + this.log.error(err); + reject(); + } + } + ); + return; + } + + // Legacy authentication flow (for non-ID types) + let form = {}; + if (body.indexOf("emailPasswordForm") !== -1) { + this.log.debug("parseEmailForm - Legacy authentication"); + form = this.extractHidden(body); + form["email"] = this.config.user; + } else { + if (this.type === "Wc") { + resolve(); + return; + } + this.log.error("No Login Form found for type: " + this.type); + this.log.debug(JSON.stringify(body)); + reject(); + return; + } + request.post( + { + url: "https://identity.vwgroup.io/signin-service/v1/" + this.clientId + "/login/identifier", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + "User-Agent": this.userAgent, + Accept: + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", + "Accept-Language": "en-US,en;q=0.9", + "Accept-Encoding": "gzip, deflate", + "x-requested-with": this.xrequest, + }, + form: form, + jar: this.jar, + gzip: true, + followAllRedirects: true, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + this.log.error("Failed to get login identifier"); + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + reject(); + return; + } + try { + if (body.indexOf("emailPasswordForm") !== -1) { + this.log.debug("emailPasswordForm2"); + + /* + const stringJson =body.split("window._IDK = ")[1].split(";")[0].replace(/\n/g, "") + const json =stringJson.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": ').replace(/'/g, '"') + const jsonObj = JSON.parse(json); + */ + form = { + _csrf: body.split("csrf_token: '")[1].split("'")[0], + email: this.config.user, + password: this.config.password, + hmac: body.split('"hmac":"')[1].split('"')[0], + relayState: body.split('"relayState":"')[1].split('"')[0], + }; + } else { + this.log.error("No Login Form found. Please check your E-Mail in the app."); + this.log.debug(JSON.stringify(body)); + reject(); + return; + } + request.post( + { + url: "https://identity.vwgroup.io/signin-service/v1/" + this.clientId + "/login/authenticate", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + "User-Agent": this.userAgent, + Accept: + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", + "Accept-Language": "en-US,en;q=0.9", + "Accept-Encoding": "gzip, deflate", + "x-requested-with": this.xrequest, + }, + form: form, + jar: this.jar, + gzip: true, + followAllRedirects: false, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + this.log.error("Failed to get login authenticate"); + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + reject(); + return; + } + + try { + this.log.debug(JSON.stringify(body)); + this.log.debug(JSON.stringify(resp.headers)); + + if ( + resp.headers.location.split("&").length <= 2 || + resp.headers.location.indexOf("/terms-and-conditions?") !== -1 + ) { + this.log.warn(resp.headers.location); + this.log.warn( + "No valid userid, please check username and password or visit this link or logout and login in your app account:", + ); + this.log.warn("Bitte in die App einloggen und die Nutzungsbedingungen akzeptieren."); + this.log.warn("https://" + resp.request.host + resp.headers.location); + this.log.warn("For Skoda: https://skodaid.vwgroup.io/landing-page"); + this.log.warn("For VW: https://vwid.vwgroup.io/landing-page"); + this.log.warn("Try to auto accept new consent"); + + request.get( + { + url: "https://" + resp.request.host + resp.headers.location, + jar: this.jar, + headers: { + "User-Agent": this.userAgent, + Accept: + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", + "Accept-Language": "en-US,en;q=0.9", + "Accept-Encoding": "gzip, deflate", + "x-requested-with": this.xrequest, + }, + followAllRedirects: true, + gzip: true, + }, + (err, resp, body) => { + this.log.debug(body); + + const form = this.extractHidden(body); + //check for empty form object + if (Object.keys(form).length === 0 && form.constructor === Object) { + try { + const stringJson = body.split("window._IDK = ")[1].split(" { + if (err && err.message.indexOf("Invalid protocol:") === 0) { + this.log.info("Auto accept succesful. Restart adapter in 10sec"); + setTimeout(() => { + this.restart(); + }, 10 * 1000); + return; + } + if ( + (err && err.message.indexOf("Invalid protocol:") !== -1) || + (resp && resp.statusCode >= 400) + ) { + this.log.warn("Failed to auto accept"); + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + reject(); + return; + } + this.log.info("Auto accept succesful. Restart adapter in 10sec"); + setTimeout(() => { + this.restart(); + }, 10 * 1000); + }, + ); + }, + ); + + reject(); + return; + } + this.config.userid = resp.headers.location.split("&")[2].split("=")[1]; + if (!this.stringIsAValidUrl(resp.headers.location)) { + if (resp.headers.location.indexOf("&error=") !== -1) { + const location = resp.headers.location; + this.log.error( + "Error: " + location.substring(location.indexOf("error="), location.length - 1), + ); + } else { + this.log.error("No valid login url, please download the log and visit:"); + this.log.error("http://" + resp.request.host + resp.headers.location); + } + reject(); + return; + } + + let getRequest = request.get( + { + url: resp.headers.location || "", + headers: { + "User-Agent": this.userAgent, + Accept: + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", + "Accept-Language": "en-US,en;q=0.9", + "Accept-Encoding": "gzip, deflate", + "x-requested-with": this.xrequest, + }, + jar: this.jar, + gzip: true, + followAllRedirects: true, + }, + (err, resp, body) => { + if (err) { + this.log.debug(err); + this.getTokens(getRequest, code_verifier, reject, resolve); + } else { + this.log.debug(body); + this.log.warn( + "No Token received visiting url and accept the permissions or login in the App and accept manually", + ); + this.log.info(getRequest.uri.href); + const form = this.extractHidden(body); + + getRequest = request.post( + { + url: getRequest.uri.href, + headers: { + "Content-Type": "application/x-www-form-urlencoded", + "User-Agent": this.userAgent, + Accept: + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", + "Accept-Language": "en-US,en;q=0.9", + "Accept-Encoding": "gzip, deflate", + "x-requested-with": this.xrequest, + referer: getRequest.uri.href, + }, + form: form, + jar: this.jar, + gzip: true, + followAllRedirects: true, + }, + (err, resp, body) => { + if (err) { + this.getTokens(getRequest, code_verifier, reject, resolve); + } else { + this.log.error( + "No Token received. Please try to logout and login in the latest MySkoda or MySeat App", + ); + try { + this.log.debug(JSON.stringify(body)); + } catch (err) { + this.log.error(err); + reject(); + } + } + }, + ); + } + }, + ); + } catch (err2) { + this.log.error( + "Login was not successful, please check your login credentials and selected type", + ); + err && this.log.error(err); + this.log.error(err2); + this.log.error(err2.stack); + reject(); + } + }, + ); + } catch (err) { + this.log.error(err); + reject(); + } + }, + ); + } catch (err) { + this.log.error(err); + reject(); + } + }, + ); + }); + } + async cleanObjects(vin) { + let remoteState = await this.getObjectAsync(vin + ".general.systemId"); + if (remoteState) { + this.log.info("clean old states" + vin); + await this.delObjectAsync(vin, { recursive: true }); + } + remoteState = await this.getObjectAsync(vin + ".general.capabilities"); + if (remoteState) { + this.log.info("clean old states" + vin); + await this.delObjectAsync(vin, { recursive: true }); + } + if (this.config.type === "seatcupra") { + remoteState = await this.getObjectAsync(vin + ".remote.targetTemperatureInCelsius"); + if (!remoteState) { + this.log.info("clean old states" + vin); + await this.delObjectAsync(vin, { recursive: true }); + } + } + } + updateStatus() { + if (this.config.type === "go") { + this.getVehicles(); + return; + } else if (this.config.type === "skodae") { + this.vinArray.forEach((vin) => { + this.getSkodaEStatus(vin); + }); + if (this.pairedWallbox) { + this.getWcData(this.config.historyLimit); + } + } else if (this.config.type === "audidata") { + this.vinArray.forEach((vin) => { + this.getAudiDataStatus(vin).catch(() => { + this.log.error("get audi data status Failed"); + }); + }); + } else if (this.config.type === "id") { + this.vinArray.forEach((vin) => { + this.getIdStatus(vin).catch(() => { + this.log.error("get id status Failed"); + this.refreshIDToken().catch(() => {}); + }); + if (this.config.type === "id" && this.pairedWallbox) { + this.getWcData(this.config.historyLimit); + } + }); + return; + } else if (this.config.type === "audietron") { + this.vinArray.forEach((vin) => { + this.getIdStatus(vin).catch(() => { + this.log.error("get id status Failed"); + this.refreshTokenv2().catch(() => {}); + }); + }); + return; + } else if (this.config.type === "seatcupra") { + this.vinArray.forEach((vin) => { + this.getSeatCupraStatus(vin); + }); + return; + } else if (this.config.type === "seatelli" || this.config.type === "skodapower") { + this.getElliData(this.config.type).catch(() => { + this.log.error("get elli Failed"); + }); + + return; + } else { + this.vinArray.forEach((vin) => { + this.statesArray.forEach((state) => { + if (state.path == "tripdata") { + this.tripTypes.forEach((tripType) => { + this.getVehicleStatus( + vin, + state.url, + state.path, + state.element, + state.element2, + null, + null, + tripType, + ).catch(() => { + this.log.debug("error while getting " + state.url); + }); + }); + } else { + this.getVehicleStatus(vin, state.url, state.path, state.element, state.element2).catch(() => { + this.log.debug("error while getting " + state.url); + }); + } + }); + }); + } + } + receiveLoginUrl() { + return new Promise((resolve, reject) => { + request( + { + method: "GET", + url: + "https://emea.bff.cariad.digital/user-login/v1/authorize?nonce=" + + this.randomString(16) + + "&redirect_uri=weconnect://authenticated", + headers: { + "user-agent": this.userAgent, + accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "accept-language": "de-de", + }, + jar: this.jar, + gzip: true, + followAllRedirects: false, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + this.log.error("Failed in receive login url "); + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + reject(); + return; + } + resolve(resp.request.href); + }, + ); + }); + } + replaceVarInUrl(url, vin, tripType) { + const curHomeRegion = this.homeRegion[vin] || "https://msg.volkswagen.de"; + return url + .replace("/$vin", "/" + vin + "") + .replace("$homeregion/", curHomeRegion + "/") + .replace("/$type/", "/" + this.type + "/") + .replace("/$country/", "/" + this.country + "/") + .replace("/$tripType", "/" + tripType); + } + getQmauth() { + const timestamp = parseInt(Date.now() / 100000); + this.log.debug(timestamp.toString()); + //credits to https://github.com/arjenvrh/audi_connect_ha/blob/master/custom_components/audiconnect/audi_services.py + const xqmauth_secret = Buffer.from([ + 26, + 256 - 74, + 256 - 103, + 37, + 256 - 84, + 23, + 256 - 102, + 256 - 86, + 78, + 256 - 125, + 256 - 85, + 256 - 26, + 113, + 256 - 87, + 71, + 109, + 23, + 100, + 24, + 256 - 72, + 91, + 256 - 41, + 6, + 256 - 15, + 67, + 108, + 256 - 95, + 91, + 256 - 26, + 71, + 256 - 104, + 256 - 100, + ]); + const xqmauth_val = crypto.createHmac("sha256", xqmauth_secret).update(timestamp.toString()).digest("hex"); + this.log.debug(timestamp.toString()); + return "v1:01da27b0:" + xqmauth_val; + } + getTokensv2(getRequest, code_verifier, reject, resolve) { + const url = getRequest.uri.query; + this.log.debug(url); + const queries = qs.parse(url); + const body = { + client_id: this.clientId, + grant_type: "authorization_code", + code: queries.code, + redirect_uri: "myaudi:///", + response_type: "token id_token", + code_verifier: code_verifier, + }; + const qmAuth = this.getQmauth(); + this.log.debug(qmAuth); + this.log.debug(JSON.stringify(body)); + + request( + { + method: "POST", + url: "https://emea.bff.cariad.digital/login/v1/idk/token", + headers: { + accept: "application/json", + "content-type": "application/x-www-form-urlencoded; charset=utf-8", + "accept-charset": "utf-8", + "x-qmauth": qmAuth, + "accept-language": "de-de", + "user-agent": this.userAgent, + }, + jar: this.jar, + gzip: true, + followAllRedirects: true, + body: qs.stringify(body), + }, + (err, resp) => { + if (err || (resp && resp.statusCode >= 400)) { + this.log.error("Failed get tokensv2. Please check your if your local time is correct"); + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + resp && resp.body && this.log.error(JSON.stringify(resp.body)); + reject(); + return; + } + const idktokens = JSON.parse(resp.body); + this.config.atoken = idktokens.access_token; + this.config.rtoken = idktokens.refresh_token; + request( + { + method: "POST", + url: "https://emea.bff.cariad.digital/login/v1/audi/token", + headers: { + accept: "application/json", + "content-type": "application/json; charset=utf-8", + "accept-charset": "utf-8", + "x-app-version": "4.13.0", + "x-app-name": "myAudi", + "accept-language": "de-de", + "user-agent": this.userAgent, + }, + jar: this.jar, + gzip: true, + followAllRedirects: false, + body: JSON.stringify({ + token: this.config.atoken, + grant_type: "id_token", + stage: "live", + config: "myaudi", + }), + }, + (err, resp) => { + if (err || (resp && resp.statusCode >= 400)) { + this.log.error("failed get audi token"); + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + reject(); + return; + } + if (this.config.type === "audi") { + // eslint-disable-next-line + this.getVWToken({}, jwtid_token, reject, resolve); + return; + } + this.aaztoken = JSON.parse(resp.body); + this.refreshTokenInterval && clearInterval(this.refreshTokenInterval); + this.refreshTokenInterval = setInterval(() => { + this.refreshTokenv2().catch(() => {}); + }, 0.9 * 60 * 60 * 1000); // 0.9hours + this.getVWToken({}, idktokens.id_token, reject, resolve); + }, + ); + }, + ); + } + getTokens(getRequest, code_verifier, reject, resolve) { + if (this.config.type === "audietron") { + this.getTokensv2(getRequest, code_verifier, reject, resolve); + return; + } + + let hash = ""; + if (getRequest.uri.hash) { + hash = getRequest.uri.hash; + } else { + hash = getRequest.uri.query; + } + const hashArray = hash.split("&"); + // eslint-disable-next-line no-unused-vars + let state; + let jwtauth_code; + let jwtaccess_token; + let jwtid_token; + let jwtstate; + hashArray.forEach((hash) => { + const harray = hash.split("="); + if (harray[0] === "#state" || harray[0] === "state") { + state = harray[1]; + } + if (harray[0] === "code") { + jwtauth_code = harray[1]; + } + if (harray[0] === "access_token") { + jwtaccess_token = harray[1]; + } + if (harray[0] === "id_token") { + jwtid_token = harray[1]; + } + if (harray[0] === "#state") { + jwtstate = harray[1]; + } + }); + // const state = hashArray[0].substring(hashArray[0].indexOf("=") + 1); + // const jwtauth_code = hashArray[1].substring(hashArray[1].indexOf("=") + 1); + // const jwtaccess_token = hashArray[2].substring(hashArray[2].indexOf("=") + 1); + // const jwtid_token = hashArray[5].substring(hashArray[5].indexOf("=") + 1); + + let method = "POST"; + let body = "auth_code=" + jwtauth_code + "&id_token=" + jwtid_token; + let url = "https://tokenrefreshservice.apps.emea.vwapps.io/exchangeAuthCode"; + let headers = { + // "user-agent": this.userAgent, + "X-App-version": this.xappversion, + "content-type": "application/x-www-form-urlencoded", + "x-app-name": this.xappname, + accept: "application/json", + }; + if (this.config.type === "vw" || this.config.type === "vwv2") { + body += "&code_verifier=" + code_verifier; + } else { + let brand = this.config.type === "skodae" ? "skoda" : this.config.type; + if (this.config.type === "seatcupra2") { + brand = "cupra"; + } + body += "&brand=" + brand; + } + if (this.config.type === "seatcupra2") { + body += "&code_verifier=" + code_verifier; + } + if (this.config.type === "skodae") { + // Parse query parameters instead of hash (VW/Skoda changed the login flow) + const parsedParameters = qs.parse(getRequest.uri.query || hash); + // this.config.atoken = parsedParameters.access_token; + let systemId = "TECHNICAL"; + if (this.clientId === "7f045eee-7003-4379-9968-9355ed2adb06@apps_vw-dilab_com") { + systemId = "CONNECT"; + } + method = "POST"; + url = + "https://mysmob.api.connect.skoda-auto.cz/api/v1/authentication/exchange-authorization-code?tokenType=" + + systemId; + body = JSON.stringify({ + code: parsedParameters.code, + redirectUri: "myskoda://redirect/login/", + verifier: code_verifier, + }); + headers = { + accept: "*/*", + // authorization: "Bearer " + parsedParameters.id_token, + "content-type": "application/json", + "user-agent": this.skodaUserAgent, + "accept-language": "de-de", + }; + } + if (this.config.type === "go") { + url = "https://dmp.apps.emea.vwapps.io/mobility-platform/token"; + body = + "code=" + + jwtauth_code + + "&client_id=" + + this.clientId + + "&redirect_uri=vwconnect://de.volkswagen.vwconnect/oauth2redirect/identitykit&grant_type=authorization_code&code_verifier=" + + code_verifier; + } + if (this.config.type === "seatcupra") { + url = "https://ola.prod.code.seat.cloud.vwgroup.com/authorization/api/v1/token"; + body = + "code=" + + jwtauth_code + + "&client_id=" + + this.clientId + + "&redirect_uri=" + + this.redirect + + "&grant_type=authorization_code&code_verifier=" + + code_verifier; + headers = { + accept: "*/*", + "content-type": "application/x-www-form-urlencoded; charset=utf-8", + authorization: "Basic OTlhNWI3N2QtYmQ4OC00ZDUzLWI0ZTUtYTUzOWM2MDY5NGEzQGFwcHNfdnctZGlsYWJfY29tOg==", + "user-agent": "SEATApp/2.5.0 (com.seat.myseat.ola; build:202410171614; iOS 15.8.3) Alamofire/5.7.0 Mobile", + "accept-language": "de-de", + }; + } + if (this.config.type === "audidata") { + url = "https://audi-global-dmp.apps.emea.vwapps.io/mobility-platform/token"; + body = + "code=" + + jwtauth_code + + "&client_id=" + + this.clientId + + "&redirect_uri=acpp://de.audi.connectplugandplay/oauth2redirect/identitykit&grant_type=authorization_code&code_verifier=" + + code_verifier; + } + if (this.config.type === "id") { + url = "https://emea.bff.cariad.digital/user-login/login/v1"; + let redirerctUri = "weconnect://authenticated"; + + body = JSON.stringify({ + state: jwtstate, + id_token: jwtid_token, + redirect_uri: redirerctUri, + region: "emea", + access_token: jwtaccess_token, + authorizationCode: jwtauth_code, + }); + // @ts-ignore + headers = { + accept: "*/*", + "content-type": "application/json", + "x-newrelic-id": "VgAEWV9QDRAEXFlRAAYPUA==", + "user-agent": this.userAgent, + "accept-language": "de-de", + "x-android-package-name": this.androidPackageName, + }; + if (this.type === "Wc") { + method = "GET"; + url = + "https://prod.emea.cbs.charging.cariad.digital/user-identity/v1/identity/login?redirect_uri=wecharge://authenticated&code=" + + jwtauth_code; + redirerctUri = "wecharge://authenticated"; + headers["x-api-key"] = "yabajourasW9N8sm+9F/oP=="; + } + } + if (this.config.type === "audi") { + this.getVWToken({}, jwtid_token, reject, resolve); + return; + } + if (this.config.type === "seatelli" || this.config.type === "skodapower") { + url = "https://api.elli.eco/identity/v1/loginOrSignupWithIdKit"; + let brand = "seat"; + let redirect = "Seat-elli-hub://opid"; + if (this.config.type === "skodapower") { + brand = "skoda"; + redirect = "skoda-hub://opid"; + } + body = JSON.stringify({ + brand: brand, + grant_type: "authorization_code", + code: jwtauth_code, + redirect_uri: redirect, + code_verifier: code_verifier, + }); + // @ts-ignore + headers = { + "Content-Type": "application/json", + Accept: "application/json", + "User-Agent": this.userAgent, + "Accept-Language": "de-DE", + }; + } + request( + { + method: method, + url: url, + headers: headers, + body: body, + jar: this.jar, + gzip: true, + followAllRedirects: false, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + this.log.error("Failed to get token"); + this.log.error("Please try an alternative type like Seat Cupra#2 or Etron"); + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + reject(); + return; + } + try { + const tokens = JSON.parse(body); + + this.getVWToken(tokens, jwtid_token, reject, resolve); + } catch (err) { + this.log.error(err); + reject(); + } + }, + ); + } + + async getVWToken(tokens, jwtid_token, reject, resolve) { + if (this.config.type !== "audi" && this.config.type !== "audietron") { + if (Object.keys(tokens).length > 0) { + this.config.atoken = tokens.access_token || tokens.accessToken; + this.config.rtoken = tokens.refresh_token || tokens.refreshToken; + } + if (this.config.type === "id" || this.config.type === "skodae") { + this.log.info(`History limit: ${this.config.historyLimit}, set to -1 to disable wallcharging login`); + if (this.config.historyLimit == -1) { + this.log.info("History limit is set to -1, no wall charging login"); + } else { + //check paired wallbox + await axios({ + method: "get", + maxBodyLength: Infinity, + url: "https://prod.emea.mobile.charging.cariad.digital/headless/charging_stations/check_paired", + headers: { + "X-Api-Version": "1", + traceparent: "00-96318317ce184ee8b7e9528586f4ffec-2b2afd705c4a4a58-01", + Authorization: "Bearer " + this.config.atoken, + Host: "prod.emea.mobile.charging.cariad.digital", + Connection: "Keep-Alive", + "User-Agent": "okhttp/4.12.0", + "X-Debug-Log": "true", + "Content-Type": "application/json", + "Accept-Language": "de-DE", + "X-Brand": this.xbrand, + "X-Platform": "android", + "X-Device-Timezone": "Europe/Berlin", + "X-Sdk-Version": "4.5.4-(2025.18.0)", + "X-Use-BffError-V2": "true", + "X-Device-Manufacturer": "google", + "X-Device-Name": "Pixel 4a", + "X-Device-OS-Name": "13", + "X-Device-OS-Version": "33", + }, + }) + .then((response) => { + if (response.data && response.data.hasPairedChargingStation) { + this.log.info("Wallbox is paired"); + this.pairedWallbox = true; + this.getWcData(this.config.historyLimit); + } else { + this.log.info("Wallbox is not paired"); + } + }) + .catch((error) => { + this.log.info('No wallbox found code: "' + error.response.status + '"'); + this.log.debug(error); + }); + } + } + if (this.config.type === "id") { + // Don't overwrite if already set from BFF token exchange + if (!this.config.atoken) { + this.config.atoken = tokens.access_token || tokens.accessToken; + } + if (!this.config.rtoken) { + this.config.rtoken = tokens.refresh_token || tokens.refreshToken; + } + + //configure for wallcharging login + + this.refreshTokenInterval = setInterval(() => { + this.refreshIDToken().catch(() => {}); + }, 0.89 * 60 * 60 * 1000); // 0.89 hours + this.log.info("ID login successfull"); + + resolve(); + return; + } + + if (this.clientId != "7f045eee-7003-4379-9968-9355ed2adb06@apps_vw-dilab_com") { + this.secondAccessToken = tokens.accessToken; + this.secondRefreshToken = tokens.refreshToken; + } + + if (this.config.type === "seatelli" || this.config.type === "skodapower") { + this.config.atoken = tokens.token; + } + if (this.config.type === "skodae") { + if (this.refreshTokenInterval) { + clearInterval(this.refreshTokenInterval); + } + this.refreshTokenInterval = setInterval(() => { + this.refreshSkodaEToken().catch(() => {}); + }, 0.9 * 60 * 60 * 1000); // 0.9hours + resolve(); + return; + } + if (this.config.type === "seatcupra") { + if (this.refreshTokenInterval) { + clearInterval(this.refreshTokenInterval); + } + this.refreshTokenInterval = setInterval(async () => { + await this.refreshSeatCupraToken().catch(() => {}); + }, 0.935 * 60 * 60 * 1000); // 0.9hours + resolve(); + return; + } + if (this.refreshTokenInterval) { + clearInterval(this.refreshTokenInterval); + } + this.refreshTokenInterval = setInterval(() => { + this.refreshToken().catch(() => { + this.log.error("Refresh Token was not successful"); + }); + if (this.secondAccessToken) { + this.refreshToken(null, true).catch(() => { + this.log.error("Refresh Second Token was not successful"); + }); + } + }, 0.9 * 60 * 60 * 1000); // 0.9hours + } + if ( + this.config.type === "go" || + this.config.type === "id" || + this.config.type === "skodae" || + this.config.type === "seatcupra" || + this.config.type === "seatelli" || + this.config.type === "skodapower" || + this.config.type === "audidata" + ) { + resolve(); + return; + } + request.post( + { + url: "https://mbboauth-1d.prd.ece.vwg-connect.com/mbbcoauth/mobile/oauth2/v1/token", + headers: { + "User-Agent": this.userAgent, + "X-App-Version": this.xappversion, + "X-App-Name": this.xappname, + "X-Client-Id": this.xclientId, + Host: "mbboauth-1d.prd.ece.vwg-connect.com", + }, + form: { + grant_type: "id_token", + token: jwtid_token || tokens.id_token, + scope: "sc2:fal", + }, + jar: this.jar, + gzip: true, + followAllRedirects: true, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + this.log.error("Failed to get VWToken"); + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + resolve(); + return; + } + try { + const tokens = JSON.parse(body); + this.config.vwatoken = tokens.access_token; + this.config.vwrtoken = tokens.refresh_token; + if (this.vwrefreshTokenInterval) { + clearInterval(this.vwrefreshTokenInterval); + } + this.vwrefreshTokenInterval = setInterval(() => { + this.refreshToken(true).catch(() => { + this.log.error("Refresh Token was not successful"); + }); + }, 0.9 * 60 * 60 * 1000); //0.9hours + resolve(); + } catch (err) { + this.log.error(err); + reject(); + } + }, + ); + } + + refreshToken(isVw, useSecondToken) { + let url = "https://tokenrefreshservice.apps.emea.vwapps.io/refreshTokens"; + let rtoken = this.config.rtoken; + if (useSecondToken) { + rtoken = this.secondRefreshToken; + } + let body = "refresh_token=" + rtoken; + let form = ""; + let brand = this.config.type === "skodae" ? "skoda" : this.config.type; + if (this.config.type === "seatcupra2") { + brand = "cupra"; + } + if (this.config.type === "vwv2") { + brand = "vw"; + } + + if (this.config.type === "seatelli") { + brand = "seat"; + } + body = "brand=" + brand + "&" + body; + let headers = { + "user-agent": this.userAgent, + "content-type": "application/x-www-form-urlencoded", + "X-App-version": this.xappversion, + "X-App-name": this.xappname, + "X-Client-Id": this.xclientId, + accept: "application/json", + }; + if (isVw) { + url = "https://mbboauth-1d.prd.ece.vwg-connect.com/mbbcoauth/mobile/oauth2/v1/token"; + rtoken = this.config.vwrtoken; + body = "grant_type=refresh_token&scope=sc2%3Afal&token=" + rtoken; //+ "&vin=" + vin; + } else if (this.config.type === "go") { + url = "https://dmp.apps.emea.vwapps.io/mobility-platform/token"; + body = ""; + // @ts-ignore + form = { + scope: "openid+profile+address+email+phone", + client_id: this.clientId, + grant_type: "refresh_token", + refresh_token: rtoken, + }; + } else if (this.config.type === "audidata") { + url = "https://audi-global-dmp.apps.emea.vwapps.io/mobility-platform/token"; + body = ""; + // @ts-ignore + form = { + scope: "openid+profile+address+email+phone", + client_id: this.clientId, + grant_type: "refresh_token", + refresh_token: rtoken, + }; + } else if (this.config.type === "seatelli" || this.config.type === "skodapower") { + url = "https://api.elli.eco/identity/v1/loginOrSignupWithIdkit"; + body = this.config.type === "seatelli" ? "seat" : "skoda"; + body = JSON.stringify({ + brand: brand, + grant_type: "refresh_token", + refresh_token: rtoken, + }); + // @ts-ignore + headers = { + "Content-Type": "application/json", + Accept: "application/json", + "user-agent": this.userAgent, + "Accept-Language": "de-DE", + }; + } + return new Promise((resolve, reject) => { + this.log.debug("refreshToken "); + this.log.debug(isVw ? "vw" : ""); + this.log.debug(`${url} ${body} ${JSON.stringify(form)}`); + request.post( + { + url: url, + headers: headers, + body: body, + form: form, + gzip: true, + followAllRedirects: true, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + this.log.error("Failing to refresh token. "); + this.log.error(isVw ? "VwToken" : ""); + err && this.log.error(err); + body && this.log.error(body); + resp && this.log.error(resp.statusCode.toString()); + this.log.error("Restart adapter in 10min"); + setTimeout(() => { + this.restart(); + }, 10 * 60 * 1000); + + reject(); + return; + } + try { + this.log.debug(url); + this.log.debug("Token refreshed"); + this.log.debug(JSON.stringify(body)); + const tokens = JSON.parse(body); + if (tokens.error) { + this.log.error(JSON.stringify(body)); + clearTimeout(this.refreshTokenTimeout); + this.refreshTokenTimeout = setTimeout(() => { + this.refreshTokenTimeout = null; + this.refreshToken(isVw).catch(() => { + this.log.error("refresh token failed"); + }); + }, 5 * 60 * 1000); + reject(); + return; + } + if (isVw) { + this.config.vwatoken = tokens.access_token; + if (tokens.refresh_token) { + this.config.vwrtoken = tokens.refresh_token; + } + } else { + if (useSecondToken) { + this.secondAccessToken = tokens.access_token; + this.secondRefreshToken = tokens.refresh_token; + resolve(); + return; + } + this.config.atoken = tokens.access_token; + if (tokens.refresh_token) { + this.config.rtoken = tokens.refresh_token; + } + if (tokens.accessToken) { + this.config.atoken = tokens.accessToken; + this.config.rtoken = tokens.refreshToken; + } + if (tokens.token) { + this.config.atoken = tokens.token; + } + } + resolve(); + } catch (err) { + this.log.error("Failing to parse refresh token. The instance will do restart and try a relogin."); + this.log.error(err); + this.log.error(JSON.stringify(body)); + this.log.error(resp.statusCode.toString()); + this.log.error(err.stack); + this.restart(); + } + }, + ); + }); + } + + async getPersonalData() { + if ( + this.config.type === "audi" || + this.config.type === "go" || + this.config.type === "audidata" || + this.config.type === "audietron" || + this.config.type === "id" || + this.config.type === "seatelli" || + this.config.type === "skodapower" + ) { + return; + } + if (this.config.type === "skodae") { + await axios({ + method: "get", + url: "https://mysmob.api.connect.skoda-auto.cz/api/v1/users", + headers: { + "User-Agent": this.skodaUserAgent, + Authorization: "Bearer " + this.config.atoken, + }, + }) + .then((response) => { + this.log.debug("get user data " + JSON.stringify(response.data)); + this.skodaUser = response.data.id; + }) + .catch((error) => { + this.log.error("get user data Failed"); + this.log.error(error); + }); + return; + } + if (this.config.type === "seatcupra") { + this.seatcupraUser = await axios({ + method: "get", + url: "https://identity-userinfo.vwgroup.io/oidc/userinfo", + headers: { + accept: "*/*", + authorization: "Bearer " + this.config.atoken, + "accept-language": "de-DE,de;q=0.9", + "user-agent": this.userAgent, + }, + }) + .then((res) => { + return res.data.sub; + }) + .catch((error) => { + this.log.error(error); + error.response && this.log.error(JSON.stringify(error.response.data)); + }); + + return; + } + // if (this.config.type === "seatcupra2") { + // this.seatcupraUser = await axios({ + // method: "get", + // url: "https://identity-userinfo.vwgroup.io/oidc/userinfo", + // headers: { + // accept: "*/*", + // authorization: "Bearer " + this.config.atoken, + // "accept-language": "de-DE,de;q=0.9", + // "user-agent": this.userAgent, + // }, + // }) + // .then((res) => { + // return res.data.sub; + // }) + // .catch((error) => { + // this.log.error(error); + // error.response && this.log.error(JSON.stringify(error.response.data)); + // }); + // if (!this.seatcupraUser) { + // return; + // } + // this.config.identifier = await axios({ + // method: "get", + // url: "https://customer-profile.apps.emea.vwapps.io/v1/customers/" + this.seatcupraUser + "/personalData", + // headers: { + // accept: "*/*", + // authorization: "Bearer " + this.config.atoken, + // "accept-language": "de-DE,de;q=0.9", + // "user-agent": this.userAgent, + // }, + // }) + // .then((res) => { + // return res.data.businessIdentifierValue; + // }) + // .catch((error) => { + // this.log.error(error); + // error.response && this.log.error(JSON.stringify(error.response.data)); + // }); + + // return; + // } + + this.log.debug("getData"); + await axios({ + method: "post", + url: + "https://profileintegrityservice.apps.emea.vwapps.io/iaa/pic/v1/users/" + this.config.userid + "/check-profile", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Authorization: this.config.atoken, + }, + data: { scopeId: "commonMandatoryFields" }, + }) + .then((res) => { + this.log.info(JSON.stringify(res.data)); + this.config.identifier = res.data.mbbUserId; + }) + .catch((error) => { + this.log.debug(error); + error.response && this.log.debug(JSON.stringify(error.response.data)); + }); + } + getHomeRegion(vin) { + return new Promise((resolve, reject) => { + this.log.debug("getHomeRegion"); + request.get( + { + url: "https://mal-1a.prd.ece.vwg-connect.com/api/cs/vds/v1/vehicles/" + vin + "/homeRegion", + headers: { + "user-agent": this.userAgent, + "X-App-version": this.xappversion, + "X-App-name": this.xappname, + authorization: "Bearer " + this.config.vwatoken, + accept: "application/json", + }, + followAllRedirects: true, + gzip: true, + json: true, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + this.log.debug(vin + ": set default homeregion"); + this.homeRegion[vin] = "https://fal-3a.prd.eu.dp.vwg-connect.com"; + this.homeRegionSetter[vin] = "https://mal-3a.prd.eu.dp.vwg-connect.com/"; + resolve(); + return; + } + try { + if (body.error) { + this.log.error(JSON.stringify(body.error)); + reject(); + } + this.log.debug(vin + ": " + JSON.stringify(body)); + this.homeRegion[vin] = "https://msg.volkswagen.de"; + if (body.homeRegion && body.homeRegion.baseUri && body.homeRegion.baseUri.content) { + if (body.homeRegion.baseUri.content !== "https://mal-1a.prd.ece.vwg-connect.com/api") { + this.homeRegion[vin] = body.homeRegion.baseUri.content.split("/api")[0].replace("mal-", "fal-"); + this.homeRegionSetter[vin] = body.homeRegion.baseUri.content.split("/api")[0]; + this.log.debug("Set URL to: " + this.homeRegion[vin]); + } + } + resolve(); + } catch (err) { + this.log.error(err); + reject(); + } + }, + ); + }); + } + + getVehicles() { + return new Promise((resolve, reject) => { + if (this.config.type === "seatelli" || this.config.type === "skodapower") { + resolve(); + return; + } + let method = "get"; + let body = {}; + let url = + "https://mal-3a.prd.eu.dp.vwg-connect.com/api/usermanagement/users/v2/users/" + + this.config.identifier + + "/vehicles"; + let headers = { + "User-Agent": this.userAgent, + "X-App-Version": this.xappversion, + "X-App-Name": this.xappname, + Authorization: "Bearer " + this.config.vwatoken, + Accept: "application/json", + }; + if (this.config.type === "go") { + url = "https://dmp.apps.emea.vwapps.io/mobility-platform/vehicles"; + // @ts-ignore + headers = { + "user-agent": "okhttp/3.9.1", + authorization: "Bearer " + this.config.atoken, + "accept-language": "de-DE", + "dmp-api-version": "v2.0", + "dmp-client-info": "Android/7.0/VW Connect/App/2.9.4", + accept: "application/json;charset=UTF-8", + }; + } + if (this.config.type === "audidata") { + url = "https://audi-global-dmp.apps.emea.vwapps.io/mobility-platform/vehicles"; + // @ts-ignore + headers = { + "user-agent": "okhttp/3.9.1", + authorization: "Bearer " + this.config.atoken, + "accept-language": "de-DE", + "dmp-api-version": "v2.0", + "dmp-client-info": this.userAgent, + accept: "application/json;charset=UTF-8", + }; + } + if (this.config.type === "audietron") { + method = "post"; + url = "https://app-api.live-my.audi.com/vgql/v1/graphql"; + // @ts-ignore + headers = { + "user-agent": this.userAgent, + authorization: "Bearer " + this.aaztoken.access_token, + "accept-language": "de-DE", + "dmp-api-version": "v2.0", + "dmp-client-info": this.userAgent, + accept: "application/json;charset=UTF-8", + }; + body = { + query: + "query vehicleList {\n userVehicles {\n vin\n mappingVin\n csid\n commissionNumber\n type\n devicePlatform\n mbbConnect\n userRole {\n role\n }\n vehicle {\n classification {\n driveTrain\n }\n }\n nickname\n }\n}", + }; + } + if (this.config.type === "id") { + url = "https://emea.bff.cariad.digital/vehicle/v1/vehicles"; + // @ts-ignore + headers = { + accept: "*/*", + "content-type": "application/json", + "content-version": "1", + "x-newrelic-id": "VgAEWV9QDRAEXFlRAAYPUA==", + "user-agent": this.userAgent, + "accept-language": "de-de", + authorization: "Bearer " + this.config.atoken, + }; + } + if (this.config.type === "skodae" || this.config.type === "skoda") { + url = + "https://mysmob.api.connect.skoda-auto.cz/api/v2/garage?connectivityGenerations=MOD1&connectivityGenerations=MOD2&connectivityGenerations=MOD3&connectivityGenerations=MOD4"; + // @ts-ignore + headers = { + accept: "application/json", + "content-type": "application/json;charset=utf-8", + "user-agent": this.skodaUserAgent, + "accept-language": "de-de", + authorization: "Bearer " + this.config.atoken, + }; + } + if (this.config.type === "seatcupra") { + url = "https://ola.prod.code.seat.cloud.vwgroup.com/v2/users/" + this.seatcupraUser + "/garage/vehicles"; + // @ts-ignore + headers = { + accept: "application/json", + "content-type": "application/json;charset=utf-8", + "user-agent": this.userAgent, + "accept-language": "de-de", + authorization: "Bearer " + this.config.atoken, + }; + } + + request( + { + method: method, + url: url, + headers: headers, + followAllRedirects: true, + gzip: true, + json: true, + ...(Object.keys(body).length && { body }), + }, + async (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + if (resp && resp.statusCode === 429) { + this.log.error( + "Too many requests. Please turn on your car to send new requests. Maybe force update/update erzwingen is too often.", + ); + } + err && this.log.error(err); + body && this.log.error(JSON.stringify(body)); + resp && this.log.error(resp.statusCode.toString()); + reject(); + return; + } + try { + if (body.errorCode) { + this.log.error(JSON.stringify(body)); + reject(); + return; + } + this.log.debug(JSON.stringify(body)); + if (this.config.type === "id") { + this.log.info("Found " + body.data.length + " vehicles"); + for (const element of body.data) { + const vin = element.vin; + await this.cleanObjects(vin); + this.log.info(`Create vehicle ${vin}`); + if (!vin) { + this.log.info("No vin found for:" + JSON.stringify(element)); + return; + } + this.vinArray.push(vin); + this.extendObject(element.vin, { + type: "device", + common: { + name: element.nickname, + role: "indicator", + type: "mixed", + write: false, + read: true, + }, + native: {}, + }); + this.extractKeys(this, vin + ".general", element); + + this.extendObject(vin + ".remote", { + type: "channel", + common: { + name: "Remote Control", + }, + native: {}, + }); + this.extendObject(vin + ".remote.charging", { + type: "state", + common: { + name: "Start/Stop Battery Charge", + type: "boolean", + role: "button", + def: false, + write: true, + }, + native: {}, + }); + + this.extendObject(vin + ".remote.climatisation", { + type: "state", + common: { + name: "Start/Stop Climatisation Target Temp => status.climatisationSettings", + type: "boolean", + role: "button", + def: false, + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.access", { + type: "state", + common: { + name: "Lock = True or Unlock = False Car", + type: "boolean", + role: "button", + def: false, + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.auxiliaryheating", { + type: "state", + common: { + name: "Standheizung Aux Heating", + type: "boolean", + role: "button", + def: false, + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.windowheating", { + type: "state", + common: { + name: "Scheibenheizung Window Heating", + type: "boolean", + role: "button", + def: false, + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.refresh", { + type: "state", + common: { + name: "Refresh Data", + type: "boolean", + role: "button", + def: false, + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.vehiclewakeuptrigger", { + type: "state", + common: { + name: "Wake up car and refresh data only every 15min allowed", + type: "boolean", + role: "button", + def: false, + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.honkandflash", { + type: "state", + common: { + name: "Hupen und Licht Honk and Flash", + type: "boolean", + role: "button", + def: false, + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.flash", { + type: "state", + common: { + name: "Licht Flash", + type: "boolean", + role: "button", + def: false, + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.lock", { + type: "state", + common: { + name: "Lock ", + type: "boolean", + role: "switch", + write: true, + }, + native: {}, + }); + + this.extendObject(vin + ".remote.lockv2", { + type: "state", + common: { + name: "Lock v2", + type: "boolean", + role: "switch", + write: true, + }, + native: {}, + }); + } + resolve(); + return; + } + if (this.config.type === "go") { + body.forEach((element) => { + const vin = element.vehicle.vin; + this.extendObject(element.vehicle.vin, { + type: "device", + common: { + name: element.licencePlate, + role: "indicator", + type: "mixed", + write: false, + + read: true, + }, + native: {}, + }); + const adapter = this; + + traverse(element).forEach(function (value) { + if (this.path.length > 0 && this.isLeaf) { + const modPath = this.path; + this.path.forEach((pathElement, pathIndex) => { + if (!isNaN(parseInt(pathElement))) { + let stringPathIndex = parseInt(pathElement) + 1 + ""; + while (stringPathIndex.length < 2) stringPathIndex = "0" + stringPathIndex; + const key = this.path[pathIndex - 1] + stringPathIndex; + const parentIndex = modPath.indexOf(pathElement) - 1; + modPath[parentIndex] = key; + modPath.splice(parentIndex + 1, 1); + } + }); + let name = this.key; + if (typeof this.key === "number") { + name = this.key.toString(); + } + adapter + .setObjectNotExistsAsync(vin + ".status." + modPath.join("."), { + type: "state", + common: { + name: name, + role: "indicator", + type: typeof value, + write: false, + read: true, + }, + native: {}, + }) + .then(() => { + if (typeof value === "object") { + value = JSON.stringify(value); + } + adapter.setState(vin + ".status." + modPath.join("."), value || this.node, true); + }) + .catch((error) => { + adapter.log.error(error); + }); + } + }); + }); + resolve(); + return; + } + if (this.config.type === "audidata") { + body.forEach(async (element) => { + const vin = element.vehicle.vin; + this.vinArray.push(vin); + await this.extendObjectAsync(vin, { + type: "device", + common: { + name: vin, + role: "indicator", + type: "string", + write: false, + read: true, + }, + native: {}, + }); + }); + resolve(); + return; + } + if (this.config.type === "seatcupra") { + this.log.info("Found " + body.vehicles.length + " vehicles"); + + for (const element of body.vehicles) { + const vin = element.vin; + await this.cleanObjects(vin); + if (!vin) { + this.log.info("No vin found for:" + JSON.stringify(element)); + return; + } + this.vinArray.push(vin); + + await this.extractKeys(this, vin + ".general", element); + await this.extendObject(element.vin, { + type: "device", + common: { + name: element.vehicleNickname, + role: "indicator", + type: "mixed", + write: false, + read: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote", { + type: "channel", + common: { + name: "Remote controls", + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.charging", { + type: "state", + common: { + name: "Start/Stop Battery Charge", + type: "boolean", + role: "button", + write: true, + }, + native: {}, + }); + + this.extendObject(vin + ".remote.climatisation", { + type: "state", + common: { + name: "Start/Stop Climatisation", + type: "boolean", + role: "button", + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.access", { + type: "state", + common: { + name: "Lock = True or Unlock = False Car", + type: "boolean", + role: "button", + def: false, + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.windowheating", { + type: "state", + common: { + name: "Scheibenheizung Window Heating", + type: "boolean", + role: "button", + def: false, + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.targetTemperatureInCelsius", { + type: "state", + common: { + name: "Air-conditioning Temp in Celsius", + type: "number", + role: "value.temperature", + write: true, + }, + native: {}, + }); + } + resolve(); + return; + } + if (this.config.type === "skodae" || this.config.type === "skoda") { + this.log.info(`Found ${body.vehicles.length} vehicles`); + + for (const element of body.vehicles) { + const vin = element.vin; + await this.cleanObjects(vin); + + const name = element.title + " " + element.licensePlate; + this.vinArray.push(vin); + await this.extendObject(element.vin, { + type: "device", + common: { + name: name, + role: "indicator", + type: "string", + write: false, + read: true, + }, + native: {}, + }); + + this.extractKeys(this, element.vin + ".general", element).catch((error) => { + this.log.error("Failed to extract"); + this.log.error(error); + }); + await this.extendObject(vin + ".mqtt", { + type: "channel", + common: { + name: "Live Events and Operation", + role: "indicator", + type: "mixed", + write: false, + read: true, + }, + native: {}, + }); + await this.extendObject(vin + ".mqtt.events", { + type: "channel", + common: { + name: "Live Events", + role: "indicator", + type: "mixed", + write: false, + read: true, + }, + native: {}, + }); + await this.extendObject(vin + ".remote", { + type: "channel", + common: { + name: "Remote controls", + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.refresh", { + type: "state", + common: { + name: "Refresh Data from Server", + type: "boolean", + role: "button", + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.charging", { + type: "state", + common: { + name: "Start/Stop Battery Charge", + type: "boolean", + role: "button", + write: true, + }, + native: {}, + }); + + this.extendObject(vin + ".remote.auxiliaryheating", { + type: "state", + common: { + name: "Standheizung Stop = 0, Start = Duration in seconds", + type: "number", + role: "level.timer", + + write: true, + def: 2400, + }, + native: {}, + }); + this.extendObject(vin + ".remote.windowheating", { + type: "state", + common: { + name: "Scheibenheizung Window Heating", + type: "boolean", + role: "button", + def: false, + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.air-conditioning", { + type: "state", + common: { + name: "Start/Stop Air-conditioning", + type: "boolean", + role: "button", + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.ventilation", { + type: "state", + common: { + name: "Start/Stop ventilation", + type: "boolean", + role: "button", + write: true, + }, + native: {}, + }); + + this.extendObject(vin + ".remote.targetTemperatureInCelsius", { + type: "state", + common: { + name: "Air-conditioning Temp in Celsius", + type: "number", + role: "value.temperature", + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.maxChargeCurrent", { + type: "state", + common: { + name: "Set maxChargeCurrent", + type: "number", + role: "number", + write: true, + }, + native: {}, + }); + + this.extendObject(vin + ".remote.access", { + type: "state", + common: { + name: "Lock = True or Unlock = False Car", + type: "boolean", + role: "button", + def: false, + write: true, + }, + native: {}, + }); + } + this.connectMqtt(); + resolve(); + return; + } + + if (this.config.type === "audietron") { + if (body.errors) { + this.log.error(JSON.stringify(body.errors)); + reject(); + return; + } + this.log.info(`Found ${body.data.userVehicles.length} vehicles`); + body.data.userVehicles.forEach(async (element) => { + const vin = element.vin; + this.vinArray.push(vin); + await this.extendObjectAsync(element.vin, { + type: "device", + common: { + name: element.nickname, + role: "indicator", + type: "string", + write: false, + read: true, + }, + native: {}, + }); + + this.extractKeys(this, element.vin + ".general", element).catch((error) => { + this.log.error("Failed to extract"); + this.log.error(error); + }); + + this.extendObject(element.vin, { + type: "device", + common: { + name: element.nickname, + role: "indicator", + type: "mixed", + write: false, + read: true, + }, + native: {}, + }); + this.extractKeys(this, vin + ".general", element); + + this.extendObject(vin + ".remote", { + type: "channel", + common: { + name: "Remote controls", + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.charging", { + type: "state", + common: { + name: "Start/Stop Battery Charge", + type: "boolean", + role: "button", + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.refresh", { + type: "state", + common: { + name: "Refresh", + type: "boolean", + role: "button", + write: true, + }, + native: {}, + }); + + this.extendObject(vin + ".remote.climatisation", { + type: "state", + common: { + name: "Start/Stop Climatisation", + type: "boolean", + role: "button", + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.climatisationv2", { + type: "state", + common: { + name: "Start/Stop Climatisation v2", + type: "boolean", + role: "switch", + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.climatisationv3", { + type: "state", + common: { + name: "Start/Stop Climatisation", + type: "boolean", + role: "button", + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.maxChargeCurrent", { + type: "state", + common: { + name: "Set maxChargeCurrent", + type: "number", + role: "number", + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.chargeMinLimit", { + type: "state", + common: { + name: "Set chargeMinLimit", + type: "number", + role: "number", + write: true, + }, + native: {}, + }); + this.extendObject(vin + ".remote.windowheating", { + type: "state", + common: { + name: "Start Windowheating", + type: "boolean", + role: "switch", + write: true, + }, + native: {}, + }); + }); + resolve(); + return; + } + if (!body.userVehicles) { + this.log.info("No Vehicles found"); + resolve(); + return; + } + const vehicles = body.userVehicles.vehicle; + this.log.info(`Found ${vehicles.length} vehicles`); + vehicles.forEach((vehicleObject) => { + let vehicle = vehicleObject; + if (vehicleObject.content) { + vehicle = vehicleObject.content; + } + this.vinArray.push(vehicle); + + this.extendObject(vehicle, { + type: "device", + common: { + name: vehicle.title, + role: "indicator", + type: "mixed", + write: false, + read: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote", { + type: "state", + common: { + name: "Remote controls", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.forceRefresh", { + type: "state", + common: { + name: "force Refresh", + type: "boolean", + role: "button", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.batterycharge", { + type: "state", + common: { + name: "Start Battery Charge", + type: "boolean", + role: "switch", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.maxChargeCurrent", { + type: "state", + common: { + name: "Set maxChargeCurrent", + type: "number", + role: "number", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.chargeMinLimit", { + type: "state", + common: { + name: "Set chargeMinLimit", + type: "number", + role: "number", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.climatisation", { + type: "state", + common: { + name: "Start/Stop Climatisation", + type: "boolean", + role: "switch", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.climatisationv2", { + type: "state", + common: { + name: "Start/Stop Climatisation v2", + type: "boolean", + role: "switch", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.climatisationv3", { + type: "state", + common: { + name: "Start/Stop Climatisation v3", + type: "boolean", + role: "switch", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.climatisationTemperature", { + type: "state", + common: { + name: "Temperature in °C", + type: "number", + role: "number", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.windowheating", { + type: "state", + common: { + name: "Start Windowheating", + type: "boolean", + role: "switch", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.honk", { + type: "state", + common: { + name: "Start Honk", + type: "boolean", + role: "button", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.flash", { + type: "state", + common: { + name: "Start Flash", + type: "boolean", + role: "button", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.standheizung", { + type: "state", + common: { + name: "Standheizung aktiviert", + type: "boolean", + role: "switch", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.lock", { + type: "state", + common: { + name: "Verriegeln (true) / Entriegeln (false)", + type: "boolean", + role: "switch", + write: true, + }, + native: {}, + }); + this.hasRemoteLock = true; + this.extendObject(vehicle + ".remote.ventilationv2", { + type: "state", + common: { + name: "Ventilation aktiviert/deaktivieren", + type: "boolean", + role: "switch", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.ventilationv3", { + type: "state", + common: { + name: "Ventilation/Standheizung Audi aktiviert/deaktivieren", + type: "boolean", + role: "switch", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.standheizungv2", { + type: "state", + common: { + name: "Standheizung aktiviert/deaktivieren", + type: "boolean", + role: "switch", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.standheizungv3", { + type: "state", + common: { + name: "Start/Stop Standheizung", + type: "boolean", + role: "button", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.lockv2", { + type: "state", + common: { + name: "Verriegeln (true) / Entriegeln (false)", + type: "boolean", + role: "switch", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.ventilation", { + type: "state", + common: { + name: "Start Ventilation", + type: "boolean", + role: "switch", + write: true, + }, + native: {}, + }); + this.extendObject(vehicle + ".remote.ventilationDuration", { + type: "state", + common: { + name: "Dauer Lüftung in min", + role: "number", + write: true, + }, + native: {}, + }); + }); + resolve(); + } catch (err) { + this.log.error(err); + this.log.error(err.stack); + this.log.error("Not able to find vehicle, did you choose the correct type in the settings?"); + reject(); + } + }, + ); + }); + } + generateRandomToken(length) { + const bytes = crypto.randomBytes(Math.ceil((length * 3) / 4)); + let token = bytes.toString("base64"); + // Convert to URL-safe base64 format + token = token.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); + return token.substring(0, length); + } + async connectMqtt() { + this.reconnectCount = 0; + if (this.mqttClient) { + this.mqttClient.end(); + await this.sleep(1000); + } + + this.log.debug("Connecting to MQTT"); + const fixedUUID = ".I3f23ae47-2eb0-43d4-b2c9-aa35c7b8cd2c"; + this.mqttClient = mqtt.connect("mqtts://mqtt.messagehub.de:8883", { + username: "android-app", + password: this.config.atoken, + clientId: `${fixedUUID}#${uuid.v4()}.$${this.skodaUser}`, + reconnectPeriod: 60000, + }); + this.mqttClient.on("connect", () => { + this.reconnectCount = 0; + this.log.debug("Connected to MQTT"); + for (const vin of this.vinArray) { + this.log.debug("Connect to MQTT for " + vin); + + this.mqttClient.subscribe(`${this.skodaUser}/${vin}/#`, (err) => { + err && this.log.error(err); + }); + } + }); + this.mqttClient.on("message", (topic, message) => { + /*Examples: + { + "version": 1, + "operation": "stop-air-conditioning", + "status": "IN_PROGRESS", + "traceId": "e063a0da2c324315b8f04477340dd4b1", + "requestId": "df538725-66ff-4644-9a5d-7f3eac8838fb" +} + { + "version": 1, + "operation": "start-window-heating", + "status": "ERROR", + "errorCode": "timeout", + "traceId": "800a74737b5a4328862d958c35b71b74", + "requestId": "5a16b265-85e7-4502-bd24-c92091c3df31" +} + { + "version": 1, + "traceId": "cd2e3695-c136-4835-8e05-7e6fc305e0b2", + "timestamp": "2024-09-11T21:06:26Z", + "producer": "SKODA_MHUB", + "name": "change-soc", + "data": { + "mode": "manual", + "state": "charging", + "soc": "74", + "chargedRange": "207", + "timeToFinish": "25", + "userId": "50f8b18c-d444-422c-998f-2b599f4f0ec7", + "vin": "TMBJB9NY6RF999999" + } +} + */ + this.log.debug("Received message on topic: " + topic + " with message: " + message.toString()); + const vin = topic.split("/")[1]; + + try { + const options = { + forceIndex: true, + // deleteBeforeUpdate: true, + }; + + const data = JSON.parse(message.toString()); + + const formattedData = data; + if (data.operation) { + options.channelName = "Last Operation"; + this.json2iob.parse(vin + ".mqtt.operation", formattedData, options); + } else if (data.data) { + options.channelName = formattedData.name; + this.json2iob.parse(vin + ".mqtt.events." + formattedData.name, formattedData, options); + } else { + options.channelName = "Last Other Message"; + this.json2iob.parse(vin + ".mqtt.other", formattedData, options); + } + } catch (error) { + this.log.debug("Error parsing message: " + error); + } + }); + this.mqttClient.on("error", (error) => { + this.log.error("MQTT Error: " + error); + }); + this.mqttClient.on("close", () => { + this.log.info("MQTT Connection closed"); + }); + this.mqttClient.on("reconnect", () => { + this.reconnectCount++; + if (this.reconnectCount > 10) { + this.log.error("Reconnect count exceeded. Stop MQTT"); + this.mqttClient.end(); + return; + } + this.log.info("MQTT Reconnecting"); + }); + this.mqttClient.on("offline", () => { + this.log.error("MQTT Offline"); + }); + } + + getIdStatus(vin) { + //eslint-disable-next-line + return new Promise(async (resolve, reject) => { + await axios({ + method: "get", + url: + "https://emea.bff.cariad.digital/vehicle/v1/vehicles/" + + vin + + "/selectivestatus?jobs=access,activeVentilation,auxiliaryHeating,batteryChargingCare,batterySupport,charging,chargingProfiles,climatisation,climatisationTimers,departureProfiles,fuelStatus,honkAndFlash,hybridCarAuxiliaryHeating,vehicleHealthWarnings,vehicleHealthInspection,vehicleLights,measurements,departureTimers", + headers: { + "content-type": "application/json", + accept: "*/*", + authorization: "Bearer " + this.config.atoken, + "accept-language": "de-DE,de;q=0.9", + "user-agent": this.userAgent, + "content-version": "1", + }, + }) + .then(async (res) => { + const timestamp = Date.now(); + this.log.debug(JSON.stringify(res.data)); + const data = {}; + for (const key in res.data) { + if (key === "userCapabilities") { + data[key] = res.data[key]; + } else { + for (const subkey in res.data[key]) { + if (data[subkey]) { + data[key + "_" + subkey] = res.data[key][subkey].value || {}; + } else { + data[subkey] = res.data[key][subkey].value || {}; + } + } + } + } + if (data.odometerStatus && data.odometerStatus.error) { + this.log.warn("Odometer Error: " + data.odometerStatus.error); + this.log.info( + "Please activate die Standortdaten freigeben und die automatische Terminvereinbarung in der VW App to receive odometer data", + ); + } + // this.extractKeys(this, vin + ".status", data); + await this.json2iob.parse(vin + ".status", data, { + forceIndex: true, + makeStateWritableWithEnding: ["settings"], + }); + this.setOtherStatesInChannelNull(vin + ".status.accessStatus", timestamp - 1000); + + if (this.config.rawJson) { + await this.extendObjectAsync(vin + ".status" + "rawJson", { + type: "state", + common: { + name: vin + ".status" + "rawJson", + role: "state", + type: "json", + write: false, + read: true, + }, + native: {}, + }); + this.setState(vin + ".status" + "rawJson", JSON.stringify(data), true); + } + resolve(); + }) + .catch((error) => { + if (error.response && error.response.status >= 500) { + this.log.info("Server not available. Please try again later:" + JSON.stringify(error.response.data)); + return; + } + this.log.error("Fetching status failed"); + this.log.error(error); + error && error.response && this.log.error(JSON.stringify(error.response.data)); + reject(); + }); + await axios({ + method: "get", + url: "https://emea.bff.cariad.digital/vehicle/v1/vehicles/" + vin + "/parkingposition", + headers: { + "content-type": "application/json", + accept: "*/*", + authorization: "Bearer " + this.config.atoken, + "accept-language": "de-DE,de;q=0.9", + "user-agent": this.userAgent, + "content-version": "1", + }, + }) + .then((res) => { + if (res.status == 200) { + this.setIsCarMoving(vin, false); + } else if (res.status == 204) { + this.setIsCarMoving(vin, true); + } + this.log.debug(JSON.stringify(res.data)); + this.extractKeys(this, vin + ".parkingposition", res.data.data); + }) + .catch((error) => { + this.log.debug(error); + this.log.debug("No parkingposition found"); + // error.response && this.log.error(JSON.stringify(error.response.data)); + }); + if (this.config.vwatoken) { + await axios({ + method: "get", + url: "https://mal-3a.prd.eu.dp.vwg-connect.com/api/bs/climatisation/v1/vehicles/" + vin + "/climater", + headers: { + "content-type": "application/json", + Accept: "application/json", + authorization: "Bearer " + this.config.vwatoken, + "accept-language": "de-DE,de;q=0.9", + "user-agent": this.userAgent, + "content-version": "1", + "X-Client-Id": this.xclientId, + "X-App-Version": this.xappversion, + "X-App-Name": this.xappname, + }, + }) + .then((res) => { + this.json2iob.parse(vin, res.data); + }) + .catch((error) => { + this.log.debug(error); + this.log.debug("No climater found"); + // error.response && this.log.error(JSON.stringify(error.response.data)); + }); + } + const currentDate = new Date().toISOString(); + const minusXDays = new Date(new Date().setDate(new Date().getDate() - this.config.lastTripDays)).toISOString(); + if (Date.now() - this.lastTripCheck > 1000 * 60 * 15) { + this.lastTripCheck = Date.now(); + if (this.config.tripShortTerm == true && !this.blockTrip[vin]) { + await axios({ + method: "get", + url: + "https://emea.bff.cariad.digital/vehicle/v1/trips/" + + vin + + "/shortterm?from=" + + minusXDays + + "&to=" + + currentDate, + headers: { + "content-type": "application/json", + accept: "*/*", + authorization: "Bearer " + this.config.atoken, + "accept-language": "de-DE,de;q=0.9", + "user-agent": this.userAgent, + "content-version": "1", + }, + }) + .then(async (res) => { + this.log.debug(JSON.stringify(res.data)); + if (res.data && res.data.data) { + //sort by id + res.data.data.sort((a, b) => { + return b.id - a.id; + }); + if (this.config.numberOfTrips > 0) { + res.data.data = res.data.data.slice(0, this.config.numberOfTrips); + } + this.json2iob.parse(vin + ".shortterm", res.data.data, { + forceIndex: true, + channelName: "shortterm trips", + }); + } + }) + .catch((error) => { + if (error.response && error.response.status >= 500) { + this.log.info("Server not available. Please try again later:"); + } + this.log.error(error); + this.log.error("No shortterm trips found please disable in your settings"); + error && error.response && this.log.error(JSON.stringify(error.response.data)); + }); + } + if (this.config.tripLongTerm == true && !this.blockTrip[vin]) { + await axios({ + method: "get", + url: + "https://emea.bff.cariad.digital/vehicle/v1/trips/" + + vin + + "/longterm?from=" + + minusXDays + + "&to=" + + currentDate, + headers: { + "content-type": "application/json", + accept: "*/*", + authorization: "Bearer " + this.config.atoken, + "accept-language": "de-DE,de;q=0.9", + "user-agent": this.userAgent, + "content-version": "1", + }, + }) + .then(async (res) => { + this.log.debug(JSON.stringify(res.data)); + if (res.data && res.data.data) { + //sort by id + res.data.data.sort((a, b) => { + return b.id - a.id; + }); + if (this.config.numberOfTrips > 0) { + res.data.data = res.data.data.slice(0, this.config.numberOfTrips); + } + this.json2iob.parse(vin + ".longterm", res.data.data, { + forceIndex: true, + channelName: "logterm trips", + }); + } + }) + .catch((error) => { + if (error.response && error.response.status >= 500) { + this.log.info("Server not available. Please try again later:"); + } + this.log.error(error); + this.log.error("No longterm trips found please disable in your settings"); + error && error.response && this.log.error(JSON.stringify(error.response.data)); + }); + } + + if (this.config.tripCyclic == true && !this.blockTrip[vin]) { + await axios({ + method: "get", + url: + "https://emea.bff.cariad.digital/vehicle/v1/trips/" + + vin + + "/cyclic?from=" + + minusXDays + + "&to=" + + currentDate, + headers: { + "content-type": "application/json", + accept: "*/*", + authorization: "Bearer " + this.config.atoken, + "accept-language": "de-DE,de;q=0.9", + "user-agent": this.userAgent, + "content-version": "1", + }, + }) + .then(async (res) => { + this.log.debug(JSON.stringify(res.data)); + if (res.data && res.data.data) { + //sort by id + res.data.data.sort((a, b) => { + return b.id - a.id; + }); + if (this.config.numberOfTrips > 0) { + res.data.data = res.data.data.slice(0, this.config.numberOfTrips); + } + this.json2iob.parse(vin + ".cyclic", res.data.data, { + forceIndex: true, + channelName: "cyclic trips", + }); + } + }) + .catch((error) => { + if (error.response && error.response.status >= 500) { + this.log.info("Server not available. Please try again later"); + } + this.log.error(error); + this.log.error("No shortterm trips found please disable in your settings"); + error && error.response && this.log.error(JSON.stringify(error.response.data)); + }); + } + + if (!this.blockTrip[vin] && this.config.lastTrips) { + await axios({ + method: "get", + url: "https://emea.bff.cariad.digital/vehicle/v1/trips/" + vin + "/shortterm/last", + headers: { + "content-type": "application/json", + accept: "*/*", + authorization: "Bearer " + this.config.atoken, + "accept-language": "de-DE,de;q=0.9", + "user-agent": this.userAgent, + "content-version": "1", + }, + }) + .then(async (res) => { + this.log.debug(JSON.stringify(res.data)); + if (res.data && res.data.data) { + this.json2iob.parse(vin + ".shorttermlast", res.data.data, { + forceIndex: false, + channelName: "last shortterm trip", + }); + } + }) + .catch((error) => { + if (error.response && error.response.status >= 500) { + this.log.info("Server not available. Please try again later:"); + } + if (error.response && error.response.status === 404) { + this.log.info( + "No last shorterm trips found. Please check if your car supports shortterm trips. Block fetching for:" + + vin, + ); + this.blockTrip[vin] = true; + return; + } + + this.log.error(error); + this.log.error("No last shortterm trips found. Block trip fetching for " + vin); + this.blockTrip[vin] = true; + error && error.response && this.log.error(JSON.stringify(error.response.data)); + }); + } + if (!this.blockTrip[vin] && this.config.lastTrips) { + await axios({ + method: "get", + url: "https://emea.bff.cariad.digital/vehicle/v1/trips/" + vin + "/longterm/last", + headers: { + "content-type": "application/json", + accept: "*/*", + authorization: "Bearer " + this.config.atoken, + "accept-language": "de-DE,de;q=0.9", + "user-agent": this.userAgent, + "content-version": "1", + }, + }) + .then(async (res) => { + this.log.debug(JSON.stringify(res.data)); + if (res.data && res.data.data) { + this.json2iob.parse(vin + ".longtermlast", res.data.data, { + forceIndex: false, + channelName: "last longterm trip", + }); + } + }) + .catch((error) => { + if (error.response && error.response.status >= 500) { + this.log.info("Server not available. Please try again later:"); + } + if (error.response && error.response.status === 404) { + this.log.info( + "No last longterm trips found. Please check if your car supports longterm trips. Block fetching for " + + vin, + ); + this.blockTrip[vin] = true; + return; + } + this.log.error(error); + this.log.error("No last longterm trips found. Block fetching for " + vin); + this.blockTrip[vin] = true; + error && error.response && this.log.error(JSON.stringify(error.response.data)); + }); + } + if (!this.blockTrip[vin] && this.config.lastTrips) { + await axios({ + method: "get", + url: "https://emea.bff.cariad.digital/vehicle/v1/trips/" + vin + "/cyclic/last", + headers: { + "content-type": "application/json", + accept: "*/*", + authorization: "Bearer " + this.config.atoken, + "accept-language": "de-DE,de;q=0.9", + "user-agent": this.userAgent, + "content-version": "1", + }, + }) + .then(async (res) => { + this.log.debug(JSON.stringify(res.data)); + if (res.data && res.data.data) { + this.json2iob.parse(vin + ".cycliclast", res.data.data, { + forceIndex: false, + channelName: "last cyclic trip", + }); + } + }) + .catch((error) => { + if (error.response && error.response.status >= 500) { + this.log.info("Server not available. Please try again later:"); + } + if (error.response && error.response.status === 404) { + this.log.info( + "No last cyclic trips found. Please check if your car supports cyclic trips. Block fetching for " + + vin, + ); + this.blockTrip[vin] = true; + return; + } + this.log.error(error); + this.log.error("No last cyclic trips found. block fetching for " + vin); + this.blockTrip[vin] = true; + error && error.response && this.log.error(JSON.stringify(error.response.data)); + }); + } + } else { + this.log.debug("Skip last trip check because of last check was less than 15min ago"); + } + }); + } + async getSeatCupraStatus(vin) { + const endpoints = [ + { + url: `https://ola.prod.code.seat.cloud.vwgroup.com/v3/vehicles/${vin}/warninglights`, + path: "warninglights", + options: { forceIndex: true }, + }, + { + url: `https://ola.prod.code.seat.cloud.vwgroup.com/v5/users/${this.seatcupraUser}/vehicles/${vin}/mycar`, + path: "status", + }, + { url: `https://ola.prod.code.seat.cloud.vwgroup.com/vehicles/${vin}/charging/status`, path: "charging" }, + { url: `https://ola.prod.code.seat.cloud.vwgroup.com/vehicles/${vin}/charging/info`, path: "charging.info" }, + { + url: `https://ola.prod.code.seat.cloud.vwgroup.com/vehicles/${vin}/climatisation/status`, + path: "climatisation", + }, + + { url: `https://ola.prod.code.seat.cloud.vwgroup.com/v2/vehicles/${vin}/status`, path: "statusv2" }, + { + url: `https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/${vin}/parkingposition`, + path: "parkingposition", + }, + { url: `https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/${vin}/mileage`, path: "mileage" }, + { url: `https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/${vin}/maintenance`, path: "maintenance" }, + { url: `https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/${vin}/ranges`, path: "ranges" }, + { + url: `https://ola.prod.code.seat.cloud.vwgroup.com/v2/vehicles/${vin}/climatisation/settings`, + path: "climatisation.settings", + }, + + { url: `https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/${vin}/measurements/engines`, path: "range" }, + //https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/VSSZZZKM/driving-data/SHORT/last + + //https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/VSSZZZKM/driving-data/SHORT?from=2023-02-24T13:34:52Z&to=2025-02-23T13:34:52Z + ]; + + //check trip data every 60min + if (Date.now() - this.lastTripCheck > 1000 * 60 * 60) { + this.lastTripCheck = Date.now(); + if (this.config.tripShortTerm == true) { + endpoints.push({ + url: `https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/${vin}/driving-data/SHORT/last`, + path: "tripShort", + }); + } + if (this.config.tripLongTerm == true) { + endpoints.push({ + url: `https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/${vin}/driving-data/LONG/`, + path: "tripLong", + }); + } + if (this.config.tripCyclic == true) { + endpoints.push({ + url: `https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/${vin}/driving-data/CYCLIC/`, + path: "tripCyclic", + }); + } + } else { + this.log.debug("Skip trip check because of last check was less than 60min ago"); + } + const headers = { + accept: "*/*", + "user-agent": this.userAgent, + "accept-language": "de-de", + authorization: "Bearer " + this.config.atoken, + }; + + for (const endpoint of endpoints) { + if (this.ignoredPaths[vin] && this.ignoredPaths[vin].includes(endpoint.path)) { + this.log.debug("Ignored path: " + endpoint.path); + continue; + } + + await axios + .get(endpoint.url, { headers }) + .then(async (response) => { + this.log.debug("Received data for " + endpoint.path); + this.log.debug(JSON.stringify(response.data)); + if (endpoint.path === "mileage") { + if (response.data && response.data.mileageKm && response.data.mileageKm === 0) { + return; + } + } + const options = endpoint.options || {}; + if (endpoint.path === "tripLong" || endpoint.path === "tripCyclic") { + //reverse data array by tripId + if (!response.data.data) { + return; + } + response.data.data.sort((a, b) => { + return b.tripId - a.tripId; + }); + options.forceIndex = true; + if (this.config.numberOfTrips > 0) { + response.data.data = response.data.data.slice(0, this.config.numberOfTrips); + } + } + this.json2iob.parse(vin + "." + endpoint.path, response.data, options); + if (this.config.rawJson) { + await this.extendObjectAsync(vin + "." + endpoint.path + "rawJson", { + type: "state", + common: { + name: vin + "." + endpoint.path + "rawJson", + role: "state", + type: "json", + write: false, + read: true, + }, + native: {}, + }); + this.setState(vin + "." + endpoint.path + "rawJson", JSON.stringify(response.data), true); + } + }) + .catch((error) => { + if (error.response && (error.response.status === 400 || error.response.status === 404)) { + this.log.info("Vehicle is not supporting: " + endpoint.path); + if (!this.ignoredPaths[vin]) { + this.ignoredPaths[vin] = []; + } + this.ignoredPaths[vin].push(endpoint.path); + return; + } + this.log.error(error); + error.response && this.log.error(JSON.stringify(error.response.data)); + }); + } + } + setSeatCupraStatus(vin, action, state) { + //eslint-disable-next-line + return new Promise(async (resolve, reject) => { + let body = {}; + let secureToken; + let url = "https://ola.prod.code.seat.cloud.vwgroup.com/vehicles/" + vin + "/" + action + "/requests/" + state; + if (action === "climatisation" && state === "start") { + url = "https://ola.prod.code.seat.cloud.vwgroup.com/v2/vehicles/" + vin + "/climatisation/start"; + const pre = this.name + "." + this.instance; + const climateStates = await this.getStatesAsync(pre + "." + vin + ".status.climatisationSettings.*"); + + const allIds = Object.keys(climateStates); + allIds.forEach((keyName) => { + const key = keyName.split(".").splice(-1)[0]; + if (key.indexOf("Timestamp") === -1) { + body[key] = climateStates[keyName].val; + } + }); + if (body.targetTemperature_C) { + body.targetTemperature = body.targetTemperature_C; + body.targetTemperatureUnit = "celsius"; + } + // body = JSON.stringify(body); + } + if (action === "access") { + //verify pin first + secureToken = await this.verifySeatPin(); + if (!secureToken) { + reject(); + return; + } + body = {}; + url = "https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/" + vin + "/access/" + state; + } + + if (action === "targetTemperatureInCelsius") { + body = { + targetTemperatureUnit: "celsius", + climatisationWithoutExternalPower: true, + targetTemperature: state, + }; + + url = `https://ola.prod.code.seat.cloud.vwgroup.com/v2/vehicles/${vin}/climatisation/settings`; + } + axios({ + method: "post", + url: url, + headers: { + accept: "*/*", + "user-agent": "SEATApp/2.5.0 (com.seat.myseat.ola; build:202410171614; iOS 15.8.3) Alamofire/5.7.0 Mobile", + "accept-language": "de-de", + authorization: "Bearer " + this.config.atoken, + "content-type": "application/json", + "User-ID": this.seatcupraUser, + SecToken: secureToken, + "app-brand": "seat", + }, + data: body, + }) + .then((response) => { + this.log.info(JSON.stringify(response.data)); + resolve(); + }) + .catch((error) => { + this.log.error("Error setting status"); + this.log.error(error); + error.response && this.log.error(error.response.status.toString()); + error.response && this.log.error(JSON.stringify(error.response.data)); + reject(); + }); + }); + } + async verifySeatPin() { + if (!this.config.pin) { + this.log.error("No pin set, please set pin in configuration"); + return; + } + return await axios({ + method: "post", + url: "https://ola.prod.code.seat.cloud.vwgroup.com/v2/users/" + this.seatcupraUser + "/spin/verify", + headers: { + "content-type": "application/json", + accept: "*/*", + authorization: "Bearer " + this.config.atoken, + "accept-language": "de-DE,de;q=0.9", + "user-agent": "SEATApp/2.5.0 (com.seat.myseat.ola; build:202410171614; iOS 15.8.3) Alamofire/5.7.0 Mobile", + "content-version": "1", + "User-ID": this.seatcupraUser, + brand: "seat", + }, + data: { + spin: this.config.pin, + }, + }) + .then((res) => { + this.log.debug(JSON.stringify(res.data)); + return res.data.securityToken; + }) + .catch((error) => { + this.log.error("Error verifying pin"); + this.log.error(error); + error.response && this.log.error(JSON.stringify(error.response.data)); + }); + } + getAudiDataStatus(vin) { + return new Promise((resolve, reject) => { + const statusArray = [ + { + path: "driverlog", + url: + "https://audi-global-dmp.apps.emea.vwapps.io/mobility-platform/vehicle/" + + vin + + "/driverlogs?page=0&limit=100&returnPollData=true", + }, + { + path: "lastParkingPosition", + url: + "https://audi-global-dmp.apps.emea.vwapps.io/mobility-platform/vehicle/" + vin + "/last-parking-position", + }, + { + path: "status", + url: "https://audi-global-dmp.apps.emea.vwapps.io/mobility-platform/vehicles", + }, + ]; + statusArray.forEach((element) => { + const url = element.url; + this.log.debug(url); + request.get( + { + url: url, + + headers: { + accept: "application/json;charset=UTF-8", + "dmp-api-version": "v2.0", + "accept-language": "de-DE", + "dmp-client-info": "Android/8.0.0/Audi Connect/App/2.5.0", + "content-type": "application/json;charset=UTF-8", + "user-agent": this.userAgent, + "If-None-Match": this.etags[url] || "", + authorization: "Bearer " + this.config.atoken, + }, + followAllRedirects: true, + gzip: true, + json: true, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + err && this.log.debug(err); + resp && this.log.debug(resp.statusCode.toString()); + body && this.log.debug(JSON.stringify(body)); + reject(); + return; + } + if (resp) { + this.etags[url] = resp.headers.etag; + if (resp.statusCode === 304) { + this.log.debug("304 No values updated"); + resolve(); + return; + } + } + let preferedName = null; + if (element.path === "status") { + body = body[0]; + } + if (element.path === "driverlog") { + preferedName = "driverLogId"; + } + this.log.debug(JSON.stringify(body)); + + try { + this.extractKeys(this, vin + "." + element.path, body, preferedName); + + resolve(); + } catch (err) { + this.log.error(err); + reject(); + } + }, + ); + }); + }); + } + async getSkodaEStatus(vin) { + const statusArray = [ + { + path: "trip-statistics", + version: "v1", + postfix: "", + params: { + offset: "0", + offsetType: "WEEK", + timezone: "Europe/Berlin", + }, + name: "tripsWeek", + }, + { + path: "trip-statistics", + version: "v1", + postfix: "", + params: { + offset: "0", + offsetType: "YEAR", + timezone: "Europe/Berlin", + }, + name: "tripsYear", + }, + { path: "vehicle-maintenance/vehicles", version: "v3", postfix: "" }, + { path: "air-conditioning", version: "v2", postfix: "" }, + { path: "air-conditioning", version: "v2", postfix: "/active-ventilation", name: "ventilation" }, + { path: "air-conditioning", version: "v2", postfix: "/auxiliary-heating", name: "auxiliary-heating" }, + { path: "air-conditioning", version: "v1", postfix: "/settings" }, + // { path: "air-conditioning", version: "v1", postfix: "/timers" }, + { path: "charging", version: "v1", postfix: "" }, + { path: "charging", version: "v1", postfix: "/settings" }, + { path: "vehicle-status", version: "v2", postfix: "" }, + { path: "maps/positions/vehicles", version: "v3", postfix: "/parking", name: "position" }, //need second auth + { path: "vehicle-status", version: "v2", postfix: "/driving-range" }, + { path: "vehicle-maintenance/vehicles", version: "v3", postfix: "/report" }, + { path: "fueling/sessions", version: "v2", postfix: "" }, + { path: "fueling/sessions", version: "v2", postfix: "/state" }, + { path: "fueling/locations", version: "v2", postfix: "" }, + { path: "fueling/sessions", version: "v2", postfix: "/latest" }, + { path: "vehicle-information", version: "v1", postfix: "" }, + ]; + + for (const status of statusArray) { + if (this.ignoredPaths[vin] && this.ignoredPaths[vin].includes(status.path + status.postfix)) { + this.log.debug("Path ignored: " + status.path + status.postfix); + continue; + } + const url = + "https://mysmob.api.connect.skoda-auto.cz/api/" + + status.version + + "/" + + status.path + + "/" + + vin + + status.postfix; + const headers = { + "x-demo-mode": "false", + accept: "application/json", + "user-agent": this.skodaUserAgent, + "accept-language": "de-de", + authorization: "Bearer " + this.config.atoken, + }; + + await axios({ + method: "get", + url: url, + headers: headers, + params: status.params, + }) + .then(async (res) => { + this.log.debug(JSON.stringify(res.data)); + + let path; + if (status.name) { + path = vin + ".status." + status.name; + } else { + path = vin + ".status." + status.path.replace("/", ""); + if (status.postfix) { + path += "." + status.postfix.replace("/", ""); + } + } + + this.log.debug(path); + this.log.debug(res.status); + if (status.name === "position") { + //{"errors":["VEHICLE_IN_MOTION"]} + if ( + res.data && + res.data.errors && + res.data.errors.includes && + res.data.errors.includes("VEHICLE_IN_MOTION") + ) { + this.setIsCarMoving(vin, true, ".status"); + } else { + this.setIsCarMoving(vin, false, ".status"); + } + } + //not empty error array + + if (res.data && res.data.errors && res.data.errors.length > 0) { + this.log.debug(JSON.stringify(res.data.errors)); + } + this.json2iob.parse(path, res.data); + if (this.config.rawJson) { + await this.extendObjectAsync(path + "rawJson", { + type: "state", + common: { + name: status.path + "rawJson", + role: "state", + type: "json", + write: false, + read: true, + }, + native: {}, + }); + this.setState(path + "rawJson", JSON.stringify(res.data), true); + } + }) + .catch((error) => { + this.log.debug(status.path + " " + status.postfix); + this.log.debug(error); + error.response && this.log.debug(JSON.stringify(error.response.data)); + + if (error.response) { + if (error.response.status === 304) { + this.log.debug("304 No values updated"); + return; + } + if (error.response.status === 403) { + if (this.firstStart) { + this.log.debug(JSON.stringify(error.response.data)); + + return; + } + } + if (error.response.status === 412) { + this.log.debug(JSON.stringify(error.response.data)); + return; + } + if (error.response.status === 404 || error.response.status === 403) { + this.log.debug("Vehicle is not supporting " + status.path + " " + status.postfix); + if (!this.ignoredPaths[vin]) { + this.ignoredPaths[vin] = []; + } + this.ignoredPaths[vin].push(status.path + status.postfix); + return; + } + if (error.response.status >= 500) { + this.log.info( + "Server not available. For endpoint " + + status.path + + status.postfix + + " Please try again later:" + + JSON.stringify(error.response.data), + ); + if (error.response && JSON.stringify(error.response.data).includes("state must not be null")) { + this.log.debug("Vehicle is not supporting " + status.path + " " + status.postfix); + if (!this.ignoredPaths[vin]) { + this.ignoredPaths[vin] = []; + } + this.ignoredPaths[vin].push(status.path + status.postfix); + return; + } + if ( + error.response && + JSON.stringify(error.response.data).includes("Internal server error") && + status.path === "air-conditioning" + ) { + this.log.debug("Vehicle is not supporting " + status.path + " " + status.postfix); + if (!this.ignoredPaths[vin]) { + this.ignoredPaths[vin] = []; + } + this.ignoredPaths[vin].push(status.path + status.postfix); + return; + } + return; + } + this.log.error(JSON.stringify(error.response.data)); + } + + this.log.error(error); + this.log.error(url); + }); + } + this.firstStart = false; + } + + setSkodaESettings(vin, action, value) { + //eslint-disable-next-line + return new Promise(async (resolve, reject) => { + let body = {}; + let url = "https://mysmob.api.connect.skoda-auto.cz/api/v2/" + action + "/" + vin + "/" + value; + if (action === "air-conditioning" && value === "start") { + body = { + targetTemperature: { + temperatureValue: 23.0, + unitInCar: "CELSIUS", + }, + heaterSource: "ELECTRIC", + airConditioningWithoutExternalPower: true, + }; + const targetTemperatureState = await this.getStateAsync( + vin + ".status.air-conditioning.targetTemperature.temperatureValue", + ); + + if (targetTemperatureState) { + body.targetTemperature.temperatureValue = targetTemperatureState.val; + } + const remoteTarget = await this.getStateAsync(vin + ".remote.targetTemperatureInCelsius"); + if (remoteTarget) { + body.targetTemperature.temperatureValue = remoteTarget.val; + } + } + if (action === "auxiliaryheating") { + const duration = value; + if (duration === 0) { + value = "stop"; + } else { + value = "start"; + } - /** - * @param {Partial} [options={}] - */ - constructor(options) { - super({ - ...options, - name: "vw-connect", - }); - this.on("ready", this.onReady.bind(this)); - // this.on("objectChange", this.onObjectChange.bind(this)); - this.on("stateChange", this.onStateChange.bind(this)); - // this.on("message", this.onMessage.bind(this)); - this.on("unload", this.onUnload.bind(this)); - - - this.jar = request.jar(); - - this.refreshTokenInterval = null; - this.vwrefreshTokenInterval = null; - this.updateInterval = null; - - this.vinArray = []; - - this.statesArray = [{ - url: "https://msg.volkswagen.de/fs-car/bs/departuretimer/v1/$type/$country/vehicles/$vin/timer", - path: "timer", - element: "timer" - }, { - url: "https://msg.volkswagen.de/fs-car/bs/climatisation/v1/$type/$country/vehicles/$vin/climater", - path: "climater", - element: "climater" - - }, { - url: "https://msg.volkswagen.de/fs-car/bs/cf/v1/$type/$country/vehicles/$vin/position", - path: "position", - element: "storedPositionResponse", - element2: "position" - - }, { - url: "https://msg.volkswagen.de/fs-car/bs/tripstatistics/v1/$type/$country/vehicles/$vin/tripdata/$tripType?type=list", - path: "tripdata", - element: "tripDataList" - }, { - url: "https://msg.volkswagen.de/fs-car/bs/vsr/v1/$type/$country/vehicles/$vin/status", - path: "status", - element: "StoredVehicleDataResponse", - element2: "vehicleData" - - }, { - url: "https://msg.volkswagen.de/fs-car/destinationfeedservice/mydestinations/v1/$type/$country/vehicles/$vin/destinations", - path: "destinations", - element: "destinations" - - }, { - url: "https://msg.volkswagen.de/fs-car/bs/batterycharge/v1/$type/$country/vehicles/$vin/charger", - path: "charger", - element: "charger" - - }, { - url: "https://msg.volkswagen.de/fs-car/bs/dwap/v1/$type/$country/vehicles/$vin/history", - path: "history" - }]; - } - - /** - * Is called when databases are connected and adapter received configuration. - */ - async onReady() { - // Initialize your adapter here - - this.setState("info.connection", false, true); - // Reset the connection indicator during startup - this.type = "VW"; - this.country = "DE"; - this.clientId = "9496332b-ea03-4091-a224-8c746b885068%40apps_vw-dilab_com"; - this.xclientId = "38761134-34d0-41f3-9a73-c4be88d7d337"; - this.scope = "openid%20profile%20mbb%20email%20cars%20birthdate%20badge%20address%20vin"; - this.redirect = "carnet%3A%2F%2Fidentity-kit%2Flogin"; - this.xrequest = "de.volkswagen.carnet.eu.eremote"; - this.responseType = "id_token%20token%20code"; - this.xappversion = "5.1.2"; - this.xappname = "eRemote"; - if (this.config.type === "skoda") { - this.type = "Skoda"; - this.country = "CZ"; - this.clientId = "7f045eee-7003-4379-9968-9355ed2adb06%40apps_vw-dilab_com"; - this.xclientId = "28cd30c6-dee7-4529-a0e6-b1e07ff90b79"; - this.scope = "openid%20profile%20phone%20address%20cars%20email%20birthdate%20badge%20dealers%20driversLicense%20mbb"; - this.redirect = "skodaconnect%3A%2F%2Foidc.login%2F"; - this.xrequest = "cz.skodaauto.connect"; - this.responseType = "code%20id_token"; - this.xappversion = "3.1.6"; - this.xappname = "cz.skodaauto.connect"; - } - if (this.config.type === "audi") { - this.type = "Audi"; - this.country = "DE"; - this.clientId = 'mmiconnect_android'; - this.xclientId = "77869e21-e30a-4a92-b016-48ab7d3db1d8"; - this.scope = 'openid profile email mbb offline_access mbbuserid myaudi selfservice:read selfservice:write'; - this.redirect = ""; - this.xrequest = ""; - this.responseType = 'token id_token'; - this.xappversion = "3.14.0"; - this.xappname = "myAudi"; - } - this.login().then(() => { - this.log.debug("Login successful"); - this.setState("info.connection", true, true); - this.getPersonalData().then(() => { - this.getVehicles().then(() => { - - this.vinArray.forEach(vin => { - this.getVehicleData(vin); - this.getVehicleRights(vin); - this.statesArray.forEach(state => { - this.getVehicleStatus(vin, state.url, state.path, state.element, state.element2).catch(()=>{ - this.log.debug("error while getting " + state.url) - }); - }); - - }); - - this.updateInterval = setInterval(() => { - this.vinArray.forEach(vin => { - this.statesArray.forEach(state => { - this.getVehicleStatus(vin, state.url, state.path, state.element, state.element2).catch(()=>{ - this.log.debug("error while getting " + state.url) - }); - }); - }); - }, this.config.interval * 60 * 1000); - - }); - - }); - }); - this.subscribeStates("*"); - } - login() { - return new Promise((resolve, reject) => { - const nonce = this.getNonce(); - const state = uuidv4(); - const [code_verifier, codeChallenge] = this.getCodeChallenge(); - - let method = "GET" - let form ={}; - let url = "https://identity.vwgroup.io/oidc/v1/authorize?client_id=" + this.clientId + "&scope=" + this.scope + "&response_type=" + this.responseType + "&redirect_uri=" + this.redirect + "&nonce=" + nonce + "&state=" + state; - if (this.config.type === "vw") { - url += "&code_challenge=" + codeChallenge + "&code_challenge_method=s256"; - } - if (this.config.type === "audi") { - url="https://id.audi.com/v1/token"; - method="POST"; - form= { client_id: this.clientId, - scope: this.scope, - response_type: this.responseType, - grant_type: 'password', - username: this.config.user, - password: this.config.password } - } - request({ - method:method, - url: url, - headers: { - "User-Agent": "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.185 Mobile Safari/537.36", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", - "Accept-Language": "en-US,en;q=0.9", - "Accept-Encoding": "gzip, deflate", - "x-requested-with": this.xrequest, - "upgrade-insecure-requests": 1 - }, - jar: this.jar, - form: form, - followAllRedirects: true - }, (err, resp, body) => { - if (err) { - this.log.error(err); - reject(); - } - - - try { - if (this.config.type === "audi") { - const tokens = JSON.parse(body) - this.getVWToken(tokens,tokens.id_token,reject,resolve); - return; - } - - const dom = new JSDOM(body); - const form = {}; - const formLogin = dom.window.document.querySelector("#emailPasswordForm"); - if (formLogin) { - this.log.debug("parseEmailForm"); - for (const formElement of dom.window.document.querySelector("#emailPasswordForm").children) { - if (formElement.type === "hidden") { - form[formElement.name] = formElement.value; - } - } - form["email"] = this.config.user; - } else { - this.log.error("No Login Form found"); - this.log.debug(body); - reject(); - } - request.post({ - url: "https://identity.vwgroup.io/signin-service/v1/" + this.clientId + "/login/identifier", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - "User-Agent": "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.185 Mobile Safari/537.36", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", - "Accept-Language": "en-US,en;q=0.9", - "Accept-Encoding": "gzip, deflate", - "x-requested-with": this.xrequest - }, - form: form, - jar: this.jar, - followAllRedirects: true - }, (err, resp, body) => { - if (err) { - this.log.error(err); - reject(); - } - try { - const dom = new JSDOM(body); - const form = {}; - const formLogin = dom.window.document.querySelector("#credentialsForm"); - if (formLogin) { - this.log.debug("parsePasswordForm"); - for (const formElement of dom.window.document.querySelector("#credentialsForm").children) { - if (formElement.type === "hidden") { - form[formElement.name] = formElement.value; - } - } - form["password"] = this.config.password; - } else { - this.log.error("No Login Form found"); - this.log.debug(body); - reject(); - } - request.post({ - url: "https://identity.vwgroup.io/signin-service/v1/" + this.clientId + "/login/authenticate", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - "User-Agent": "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.185 Mobile Safari/537.36", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", - "Accept-Language": "en-US,en;q=0.9", - "Accept-Encoding": "gzip, deflate", - "x-requested-with": this.xrequest - }, - form: form, - jar: this.jar, - followAllRedirects: false, - }, (err, resp, body) => { - if (err) { - this.log.error(err); - reject(); - } - - try { - this.log.debug(JSON.stringify(body)); - this.log.debug(JSON.stringify(resp.headers)); - this.config.userid = resp.headers.location.split("&")[2].split("=")[1]; - let getRequest = request.get({ - url: resp.headers.location, - headers: { - "User-Agent": "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.185 Mobile Safari/537.36", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", - "Accept-Language": "en-US,en;q=0.9", - "Accept-Encoding": "gzip, deflate", - "x-requested-with": this.xrequest - }, - jar: this.jar, - followAllRedirects: true, - }, (err, resp, body) => { - - if (err) { - this.getTokens(getRequest, code_verifier, reject, resolve); - } else { - this.log.debug("No Token received visiting url and accept the permissions."); - const dom = new JSDOM(body); - let form = ""; - for (const formElement of dom.window.document.querySelectorAll("input")) { - if (formElement.type === "hidden") { - form += formElement.name + "=" + formElement.value + "&"; - } - } - getRequest = request.post({ - url: getRequest.uri.href, - headers: { - "Content-Type": "application/x-www-form-urlencoded", - "User-Agent": "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.185 Mobile Safari/537.36", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", - "Accept-Language": "en-US,en;q=0.9", - "Accept-Encoding": "gzip, deflate", - "x-requested-with": this.xrequest, - "referer": getRequest.uri.href - }, - form: form, - jar: this.jar, - followAllRedirects: true, - }, (err, resp, body) => { - if (err) { - this.getTokens(getRequest, code_verifier, reject, resolve); - - } else { - this.log.error("No Token received."); - try { - this.log.debug(body); - } catch (error) { - this.log.error(error); - reject(); - } - } - - }); - } - }); - } catch (error) { - this.log.error(error); - reject(); - } - }); - } catch (error) { - this.log.error(error); - reject(); - } - }); - } catch (error) { - this.log.error(error); - reject(); - } - }); - }); - } - - replaceVarInUrl(url, vin) { - return url.replace("/$vin/", "/" + vin + "/").replace("/$type/", "/" + this.type + "/").replace("/$country/", "/" + this.country + "/").replace("/$tripType", "/" + this.config.tripType); - } - getTokens(getRequest, code_verifier, reject, resolve) { - const hashArray = getRequest.uri.hash.split("&"); - let state; - let jwtauth_code; - let jwtaccess_token; - let jwtid_token; - hashArray.forEach(hash => { - const harray = hash.split("="); - if (harray[0] === "#state") { - state = harray[1]; - } - if (harray[0] === "code") { - jwtauth_code = harray[1]; - } - if (harray[0] === "access_token") { - jwtaccess_token = harray[1]; - } - if (harray[0] === "id_token") { - jwtid_token = harray[1]; - } - }); - // const state = hashArray[0].substring(hashArray[0].indexOf("=") + 1); - // const jwtauth_code = hashArray[1].substring(hashArray[1].indexOf("=") + 1); - // const jwtaccess_token = hashArray[2].substring(hashArray[2].indexOf("=") + 1); - // const jwtid_token = hashArray[5].substring(hashArray[5].indexOf("=") + 1); - let body = "auth_code=" + jwtauth_code + "&id_token=" + jwtid_token; - if (this.config.type === "vw") { - body += "&code_verifier=" + code_verifier; - } else { - body += "&brand=" + this.config.type; - } - request.post({ - url: "https://tokenrefreshservice.apps.emea.vwapps.io/exchangeAuthCode", - headers: { - // "user-agent": "okhttp/3.7.0", - "X-App-version": this.xappversion, - "content-type": "application/x-www-form-urlencoded", - "x-app-name": this.xappname, - accept: "application/json" - }, - body: body, - jar: this.jar, - followAllRedirects: false, - }, (err, resp, body) => { - if (err) { - this.log.error(err); - reject(); - } - try { - const tokens = JSON.parse(body); - this.getVWToken(tokens, jwtid_token, reject, resolve); - } catch (error) { - this.log.error(error); - reject(); - } - }); - } - - getVWToken(tokens, jwtid_token, reject, resolve) { - this.config.atoken = tokens.access_token; - this.config.rtoken = tokens.refresh_token; - this.refreshTokenInterval = setInterval(() => { - this.refreshToken().catch(() => { - setTimeout(() => { - this.refreshToken(); - }, 5 * 60 * 1000); - }); - }, 0.9 *60 * 60 * 1000); // 0.9hours - request.post({ - url: "https://mbboauth-1d.prd.ece.vwg-connect.com/mbbcoauth/mobile/oauth2/v1/token", - headers: { - "User-Agent": "okhttp/3.7.0", - "X-App-Version": this.xappversion, - "X-App-Name": this.xappname, - "X-Client-Id": this.xclientId, - Host: "mbboauth-1d.prd.ece.vwg-connect.com", - }, - form: { - grant_type: "id_token", - token: jwtid_token, - scope: "sc2:fal" - }, - jar: this.jar, - followAllRedirects: true, - }, (err, resp, body) => { - if (err) { - this.log.error(err); - reject(); - } - try { - const tokens = JSON.parse(body); - this.config.vwatoken = tokens.access_token; - this.config.vwrtoken = tokens.refresh_token; - this.vwrefreshTokenInterval = setInterval(() => { - this.refreshToken(true).catch(() => { - setTimeout(() => { - this.refreshToken(true); - }, 5 * 60 * 1000); - }); - }, 0.9 * 60 * 60 * 1000); //0.9hours - resolve(); - } - catch (error) { - this.log.error(error); - reject(); - } - }); - } - - refreshToken(isVw) { - let url = "https://tokenrefreshservice.apps.emea.vwapps.io/refreshTokens"; - let rtoken = this.config.rtoken; - let body = "refresh_token=" + rtoken; - let form= {}; - if (isVw) { - url = "https://mbboauth-1d.prd.ece.vwg-connect.com/mbbcoauth/mobile/oauth2/v1/token"; - rtoken = this.config.vwrtoken; - body = "grant_type=refresh_token&scope=sc2%3Afal&token=" + rtoken; - } - if (this.config.type==="audi") { - url= 'https://id.audi.com/v1/token'; - body =""; - form ={ client_id: this.clientId, - grant_type: 'refresh_token', - response_type: 'token id_token', - refresh_token: rtoken } - } - return new Promise((resolve, reject) => { - this.log.debug("refreshToken"); - request.post({ - url: url, - headers: { - "user-agent": "okhttp/3.7.0", - "content-type": "application/x-www-form-urlencoded", - "X-App-version": this.xappversion, - "X-App-name": this.xappname, - "X-Client-Id": this.xclientId, - "accept": "application/json" - }, - body: body, - form: form, - followAllRedirects: true - }, (err, resp, body) => { - if (err) { - this.log.error("Failing to refresh token."); - this.log.error(err); - reject(); - return; - } - try { - const tokens = JSON.parse(body); - if (isVw) { - - this.config.vwatoken = tokens.access_token; - this.config.vwrtoken = tokens.refresh_token; - - } else { - this.config.atoken = tokens.access_token; - this.config.rtoken = tokens.refresh_token; - } - resolve(); - - } catch (error) { - this.log.error("Failing to parse refresh token. Restart instance for relogin."); - this.log.error(error); - this.log.error(body); - this.log.error(error.stack); - this.restart(); - reject(); - } - }); - }); - } - - getPersonalData() { - return new Promise((resolve, reject) => { - if (this.config.type ==="audi") { - resolve(); - return; - } - this.log.debug("getData"); - request.get({ - url: "https://customer-profile.apps.emea.vwapps.io/v1/customers/" + this.config.userid + "/personalData", - headers: { - "user-agent": "okhttp/3.7.0", - "X-App-version": this.xappversion, - "X-App-name": this.xappname, - authorization: "Bearer " + this.config.atoken, - accept: "application/json", - Host: "customer-profile.apps.emea.vwapps.io" - }, - followAllRedirects: true - }, (err, resp, body) => { - if (err) { - this.log.error(err); - reject(); - return; - } - try { - if (body.error) { - this.log.error(JSON.stringify(body.error)); - reject(); - } - this.log.debug(body); - const data = JSON.parse(body); - this.config.identifier = data.businessIdentifierValue; - Object.keys(data).forEach(key => { - this.setObjectNotExists("personal." + key, { - type: "state", - common: { - name: key, - role: "indicator", - type: "mixed", - write: false, - read: true - }, - native: {} - }); - this.setState("personal." + key, data[key], true); - }); - - resolve(); - - } catch (error) { - this.log.error(error); - reject(); - } - }); - }); - } - - getCarData() { - return new Promise((resolve, reject) => { - this.log.debug("getData"); - request.get({ - url: "https://customer-profile.apps.emea.vwapps.io/v1/customers/" + this.config.userid + "/realCarData", - headers: { - "user-agent": "okhttp/3.7.0", - "X-App-version": this.xappversion, - "X-App-name": this.xappname, - authorization: "Bearer " + this.config.atoken, - accept: "application/json", - Host: "customer-profile.apps.emea.vwapps.io" - }, - followAllRedirects: true - }, (err, resp, body) => { - if (err) { - this.log.error(err); - reject(); - return; - } - try { - if (body.error) { - this.log.error(JSON.stringify(body.error)); - reject(); - } - this.log.debug(body); - const data = JSON.parse(body); - Object.keys(data).forEach(key => { - this.setObjectNotExists("car." + key, { - type: "state", - common: { - name: key, - role: "indicator", - type: "mixed", - write: false, - read: true - }, - native: {} - }); - this.setState("car." + key, data[key], true); - }); - - resolve(); - - } catch (error) { - this.log.error(error); - reject(); - } - }); - }); - } - - getVehicles() { - return new Promise((resolve, reject) => { - const url = this.replaceVarInUrl("https://msg.volkswagen.de/fs-car/usermanagement/users/v1/$type/$country/vehicles"); - request.get({ - url: url, - headers: { - "User-Agent": "okhttp/3.7.0", - Host: "msg.volkswagen.de", - "X-App-Version": this.xappversion, - "X-App-Name": this.xappname, - Authorization: "Bearer " + this.config.vwatoken, - Accept: "application/json" - }, - followAllRedirects: true, - gzip: true, - json: true, - }, (err, resp, body) => { - if (err) { - this.log.error(err); - reject(); - } - try { - if (body.errorCode) { - this.log.error(JSON.stringify(body)); - reject(); - return; - } - this.log.debug(body); - const vehicles = body.userVehicles.vehicle; - vehicles.forEach(vehicle => { - this.vinArray.push(vehicle); - this.setObjectNotExists(vehicle, { - type: "device", - common: { - name: vehicle.title, - role: "indicator", - type: "mixed", - write: false, - read: true - }, - native: {} - }); - this.setObjectNotExists(vehicle + ".remote", { - type: "state", - common: { - name: "Remote controls", - write: true, - }, - native: {} - }); - this.setObjectNotExists(vehicle + ".remote.batterycharge", { - type: "state", - common: { - name: "Start Battery Charge", - type: "boolean", - role: "button", - write: true, - }, - native: {} - }); - this.setObjectNotExists(vehicle + ".remote.climatisation", { - type: "state", - common: { - name: "Start Climatisation", - type: "boolean", - role: "button", - write: true, - }, - native: {} - }); - this.setObjectNotExists(vehicle + ".remote.climatisationTemperature", { - type: "state", - common: { - name: "Temperature in °C", - type: "boolean", - role: "number", - write: true, - }, - native: {} - }); - this.setObjectNotExists(vehicle + ".remote.windowheating", { - type: "state", - common: { - name: "Start Windowheating", - type: "boolean", - role: "button", - write: true, - }, - native: {} - }); - this.setObjectNotExists(vehicle + ".remote.honk", { - type: "state", - common: { - name: "Start Honk", - type: "boolean", - role: "button", - write: true, - }, - native: {} - }); - this.setObjectNotExists(vehicle + ".remote.flash", { - type: "state", - common: { - name: "Start Flash", - type: "boolean", - role: "button", - write: true, - }, - native: {} - }); - - }); - resolve(); - - } catch (error) { - this.log.error(error); - this.log.error(error.stack); - this.log.error("Not able to find vehicle, did you choose the correct type?.") - reject(); - } - }); - }); - } - - getVehicleData(vin) { - return new Promise((resolve, reject) => { - let url = this.replaceVarInUrl("https://msg.volkswagen.de/fs-car/vehicleMgmt/vehicledata/v2/$type/$country/vehicles/$vin/", vin); - if (this.config.type !== "vw") { - url = this.replaceVarInUrl("https://msg.volkswagen.de/fs-car/promoter/portfolio/v1/$type/$country/vehicle/$vin/carportdata", vin); - } - let atoken = this.config.vwatoken; - if (this.config.type === "audi") { - url = "https://msg.audi.de/myaudi/vehicle-management/v1/vehicles"; - atoken = this.config.atoken; - } - request.get({ - url: url, - headers: { - "User-Agent": "okhttp/3.7.0", - "X-App-Version": this.xappversion, - "X-App-Name": this.xappname, - "X-Market": "de_DE", - Authorization: "Bearer " + atoken, - Accept: "application/json, application/vnd.vwg.mbb.vehicleDataDetail_v2_1_0+xml, application/vnd.vwg.mbb.genericError_v1_0_2+xml" - }, - followAllRedirects: true, - gzip: true, - json: true, - }, (err, resp, body) => { - if (err) { - this.log.error(err); - reject(); - return; - } - try { - - this.log.debug(body); - const adapter = this; - let result = body.vehicleData; - if (this.config.type ==="audi") { - result = body.vehicles[this.vinArray.indexOf(vin)] - } - traverse(result).forEach(function (value) { - if (this.path.length > 0 && this.isLeaf) { - const modPath = this.path; - this.path.forEach((pathElement, pathIndex) => { - if (!isNaN(parseInt(pathElement))) { - let stringPathIndex = parseInt(pathElement) + 1 + ""; - while (stringPathIndex.length < 2) stringPathIndex = "0" + stringPathIndex; - const key = this.path[pathIndex - 1] + stringPathIndex; - const parentIndex = modPath.indexOf(pathElement) - 1; - modPath[parentIndex] = key; - modPath.splice(parentIndex + 1, 1); - } - }); - adapter.setObjectNotExists(vin + ".general." + modPath.join("."), { - type: "state", - common: { - name: this.key, - role: "indicator", - type: "mixed", - write: false, - read: true - }, - native: {} - }); - adapter.setState(vin + ".general." + modPath.join("."), value || this.node, true); - } - }); - - resolve(); - - } catch (error) { - this.log.error(error); - reject(); - } - }); - }); - } - - getVehicleRights(vin) { - return new Promise((resolve, reject) => { - - let url = "https://mal-1a.prd.ece.vwg-connect.com/api/rolesrights/operationlist/v3/vehicles/" + vin; - if (this.config.type === "vw") { - url += "/users/" + this.config.identifier - } - request.get({ - url: url, - qs: { - scope: "All" - }, - headers: { - "User-Agent": "okhttp/3.7.0", - "X-App-Version": this.xappversion, - "X-App-Name": this.xappname, - Authorization: "Bearer " + this.config.vwatoken, - Accept: "application/json, application/vnd.vwg.mbb.operationList_v3_0_2+xml, application/vnd.vwg.mbb.genericError_v1_0_2+xml" - }, - followAllRedirects: true, - gzip: true, - json: true, - }, (err, resp, body) => { - if (err) { - this.log.error(err); - reject(); - return; - } - try { - - if (!this.config.rights) { - resolve(); - return; - } - const adapter = this; - traverse(body.operationList).forEach(function (value) { - if (this.path.length > 0 && this.isLeaf) { - const modPath = this.path; - this.path.forEach((pathElement, pathIndex) => { - if (!isNaN(parseInt(pathElement))) { - let stringPathIndex = parseInt(pathElement) + 1 + ""; - while (stringPathIndex.length < 2) stringPathIndex = "0" + stringPathIndex; - const key = this.path[pathIndex - 1] + stringPathIndex; - const parentIndex = modPath.indexOf(pathElement) - 1; - modPath[parentIndex] = key; - modPath.splice(parentIndex + 1, 1); - } - }); - if (modPath[modPath.length - 1] !== "$") { - adapter.setObjectNotExists(vin + ".rights." + modPath.join("."), { - type: "state", - common: { - name: this.key, - role: "indicator", - type: "mixed", - write: false, - read: true - }, - native: {} - }); - adapter.setState(vin + ".rights." + modPath.join("."), value || this.node, true); - } - } - }); - - resolve(); - - } catch (error) { - this.log.error(error); - reject(); - } - }); - }); - } - getVehicleStatus(vin, url, path, element, element2) { - return new Promise((resolve, reject) => { - url = this.replaceVarInUrl(url, vin); - request.get({ - url: url, - headers: { - "User-Agent": "okhttp/3.7.0", - Host: "msg.volkswagen.de", - "X-App-Version": this.xappversion, - "X-App-Name": this.xappname, - Authorization: "Bearer " + this.config.vwatoken, - "Accept-charset": "UTF-8", - Accept: "application/json, application/vnd.vwg.mbb.VehicleStatusReport_v1_0_0+xml, application/vnd.vwg.mbb.climater_v1_0_0+xml, application/vnd.vwg.mbb.carfinderservice_v1_0_0+xml, application/vnd.volkswagenag.com-error-v1+xml, application/vnd.vwg.mbb.genericError_v1_0_2+xml, */*" - - }, - followAllRedirects: true, - gzip: true, - json: true - }, (err, resp, body) => { - if (err) { - this.log.error(err); - reject(); - return; - } - try { - - this.log.debug(JSON.stringify(body)); - - if (body === undefined || body === "" || body.error) { - if (body.error && body.error.description.indexOf("Token expired") !== -1) { - this.log.error("Error response try to refresh token " + path); - this.refreshToken(true); - } else { - this.log.debug("Not able to get " + path); - } - this.log.debug(body); - reject(); - return; - } - // const parser = new xml2js.Parser({ - // explicitArray: false, - // explicitRoot: false, - // mergeAttrs: true, - // tagNameProcessors: [xml2js.processors.stripPrefix], - // attrNameProcessors: [xml2js.processors.stripPrefix] - // }); - - const adapter = this; - let result = body; - if (result === "") { - resolve(); - return; - } - if (result) { - if (element) { - result = result[element]; - } - if (element2) { - result = result[element2]; - } - if (path === "tripdata") { - adapter.setObjectNotExists(vin + "." + path + ".lastTrip", { - type: "state", - common: { - name: "numberOfLastTrip", - role: "indicator", - type: "mixed", - write: false, - read: true - }, - native: {} - }); - adapter.setState(vin + "." + path + ".lastTrip", result.tripData.length, true); - } - - - traverse(result).forEach(function (value) { - if (this.path.length > 0 && this.isLeaf) { - const modPath = this.path; - this.path.forEach((pathElement, pathIndex) => { - if (!isNaN(parseInt(pathElement))) { - let stringPathIndex = parseInt(pathElement) + 1 + ""; - while (stringPathIndex.length < 2) stringPathIndex = "0" + stringPathIndex; - const key = this.path[pathIndex - 1] + stringPathIndex; - const parentIndex = modPath.indexOf(pathElement) - 1; - modPath[parentIndex] = key; - modPath.splice(parentIndex + 1, 1); - } - }); - - adapter.setObjectNotExists(vin + "." + path + "." + modPath.join("."), { - type: "state", - common: { - name: this.key, - role: "indicator", - type: "mixed", - write: false, - read: true - }, - native: {} - }); - adapter.setState(vin + "." + path + "." + modPath.join("."), value || this.node, true); - } else if ((path === "status" || path ==="tripdata" ) && this.path.length > 0 && !isNaN(this.path[this.path.length - 1])) { - const modPath = this.path; - this.path.forEach((pathElement, pathIndex) => { - if (!isNaN(parseInt(pathElement))) { - let stringPathIndex = parseInt(pathElement) + 1 + ""; - while (stringPathIndex.length < 2) stringPathIndex = "0" + stringPathIndex; - const key = this.path[pathIndex - 1] + stringPathIndex; - const parentIndex = modPath.indexOf(pathElement) - 1; - modPath[parentIndex] = key; - - modPath.splice(parentIndex + 1, 1); - } - }); - - if (this.node.field && this.node.field[this.node.field.length -1].textId) { - adapter.setObjectNotExists(vin + "." + path + "." + modPath.join("."), { - type: "state", - common: { - name: this.node.field[this.node.field.length -1].textId, - role: "indicator", - type: "mixed", - write: false, - read: true - }, - native: {} - }); - } - if (this.node.textId) { - adapter.setObjectNotExists(vin + "." + path + "." + modPath.join("."), { - type: "state", - common: { - name: this.node.textId, - role: "indicator", - type: "mixed", - write: false, - read: true - }, - native: {} - }); - } else if (this.node.timestamp) { - adapter.setObjectNotExists(vin + "." + path + "." + modPath.join("."), { - type: "state", - common: { - name: this.node.timestamp, - role: "indicator", - type: "mixed", - write: false, - read: true - }, - native: {} - }); - } - - - } - }); - resolve(); - } else { - this.log.error("Cannot find vehicle data " + path); - this.log.error(body); - reject(); - } - - - - - - } catch (error) { - this.log.error(error); - this.log.error(error.stack); - reject(); - } - }); - }); - } - - setVehicleStatus(vin, url, body, contentType) { - return new Promise((resolve, reject) => { - url = this.replaceVarInUrl(url, vin); - this.log.debug(body); - this.log.debug(contentType); - request.post({ - url: url, - headers: { - "User-Agent": "okhttp/3.7.0", - Host: "msg.volkswagen.de", - "X-App-Version": this.xappversion, - "X-App-Name": this.xappname, - Authorization: "Bearer " + this.config.vwatoken, - "Accept-charset": "UTF-8", - "Content-Type": contentType, - Accept: "application/json, application/vnd.vwg.mbb.ChargerAction_v1_0_0+xml,application/vnd.volkswagenag.com-error-v1+xml,application/vnd.vwg.mbb.genericError_v1_0_2+xml, */*" - }, - body: body, - followAllRedirects: true, - gzip: true, - }, (err, resp, body) => { - if (err) { - this.log.error(err); - reject(); - return; - } - try { - this.log.debug(body); - if (body.indexOf("") !== -1) { - this.log.error("Error response try to refresh token " + url); - this.log.error(body); - this.refreshToken(true); - reject(); - return; - } - this.log.info(body); - } catch (error) { - this.log.error(error); - this.log.error(error.stack); - reject(); - } - }); - }); - } - getCodeChallenge() { - let hash = ""; - let result = ""; - while (hash === "" || hash.indexOf("+") !== -1 || hash.indexOf("/") !== -1 || hash.indexOf("=") !== -1 || result.indexOf("+") !== -1 || result.indexOf("/") !== -1) { - const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - result = ""; - for (let i = 64; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)]; - result = Buffer.from(result).toString("base64"); - result = result.replace(/=/g, ""); - hash = crypto.createHash("sha256").update(result).digest("base64"); - hash = hash.slice(0, hash.length - 1); - - } - return [result, hash]; - } - getNonce() { - const timestamp = Date.now(); - let hash = crypto.createHash("sha256").update(timestamp.toString()).digest("base64"); - hash = hash.slice(0, hash.length - 1); - return hash; - } - - /** - * Is called when adapter shuts down - callback has to be called under any circumstances! - * @param {() => void} callback - */ - onUnload(callback) { - try { - this.log.info("cleaned everything up..."); - clearInterval(this.refreshTokenInterval); - clearInterval(this.vwrefreshTokenInterval); - clearInterval(this.updateInterval); - callback(); - } catch (e) { - callback(); - } - } - - /** - * Is called if a subscribed state changes - * @param {string} id - * @param {ioBroker.State | null | undefined} state - */ - async onStateChange(id, state) { - if (state) { - - if (!state.ack) { - const vin = id.split(".")[2]; - let body = ""; - let contentType = ""; - if (id.indexOf("remote") !== -1) { - const action = id.split(".")[4]; - if (action === "batterycharge") { - body = '\n\n start\n'; - if (state.val === false) { - body = '\n\n stop\n'; - } - contentType = "application/vnd.vwg.mbb.ChargerAction_v1_0_0+xml"; - this.setVehicleStatus(vin, "https://msg.volkswagen.de/fs-car/bs/batterycharge/v1/$type/$country/vehicles/$vin/charger/actions", body, contentType); - } - - if (action === "climatisation") { - body = '\n\n startClimatisation\n'; - if (state.val === false) { - body = '\n\n stopClimatisation\n'; - } - contentType = "application/vnd.vwg.mbb.ClimaterAction_v1_0_0+xml"; - this.setVehicleStatus(vin, "https://msg.volkswagen.de/fs-car/bs/climatisation/v1/$type/$country/vehicles/$vin/climater/actions", body, contentType); - } - - if (action === "climatisationTemperature") { - let temp = 2950; - if (state.val && !isNaN(state.val)) { - temp = (parseFloat(state.val) + 273, 6) * 10; - } - body = '\n\n setSettings ' + temp + " false electric \n"; - contentType = "application/vnd.vwg.mbb.ClimaterAction_v1_0_0+xml"; - this.setVehicleStatus(vin, "https://msg.volkswagen.de/fs-car/bs/climatisation/v1/$type/$country/vehicles/$vin/climater/actions", body, contentType); - } - - if (action === "windowheating") { - body = '\n\n startWindowHeating\n'; - if (state.val === false) { - body = '\n\n stopWindowHeating\n'; - } - contentType = "application/vnd.vwg.mbb.ClimaterAction_v1_0_0+xml"; - this.setVehicleStatus(vin, "https://msg.volkswagen.de/fs-car/bs/climatisation/v1/$type/$country/vehicles/$vin/climater/actions", body, contentType); - } - if (action === "flash") { - //HONK_AND_FLASH - const idArray = id.split("."); - idArray.pop(); - idArray.pop(); - idArray.push("position.carCoordinate") - const posId = idArray.join("."); - const longitude = await this.getStateAsync(posId + ".longitude"); - const latitude = await this.getStateAsync(posId + ".latitude"); - body = '{"honkAndFlashRequest":{"serviceOperationCode":"FLASH_ONLY","userPosition":{"latitude":'+latitude.val+',"longitude":'+longitude.val+'}}}'; - contentType = 'application/json; charset=UTF-8'; - this.setVehicleStatus(vin, "https://msg.volkswagen.de/fs-car/bs/rhf/v1/$type/$country/vehicles/$vin/honkAndFlash", body, contentType); - } - - if (action === "honk") { - // - const idArray = id.split("."); - idArray.pop(); - idArray.pop(); - idArray.push("position.carCoordinate") - const posId = idArray.join("."); - const longitude = await this.getStateAsync(posId + ".longitude"); - const latitude = await this.getStateAsync(posId + ".latitude"); - body = '{"honkAndFlashRequest":{"serviceOperationCode":"HONK_AND_FLASH","userPosition":{"latitude":'+latitude.val+',"longitude":'+longitude.val+'}}}'; - contentType = 'application/json; charset=UTF-8'; - this.setVehicleStatus(vin, "https://msg.volkswagen.de/fs-car/bs/rhf/v1/$type/$country/vehicles/$vin/honkAndFlash", body, contentType); - } - } - } else { - if (id.indexOf("carCoordinate.latitude") !== -1 && state.ts === state.lc && this.config.reversePos) { - - const vin = id.split(".")[2]; - const longitude = await this.getStateAsync(id.replace("latitude", "longitude")); - const longitudeValue = parseFloat(longitude.val); - - request.get({ - url: "https://nominatim.openstreetmap.org/reverse?lat=" + state.val / 1000000 + "&lon=" + longitudeValue / 1000000 + "&format=json", - - headers: { - "User-Agent": "ioBroker/vw-connect" - }, - json: true, - followAllRedirects: true - }, (err, resp, body) => { - if (err) { - this.log.error(err); - } - if (body.display_name) { - try { - const number = body.address.house_number || ""; - const city = body.address.city || body.address.town || body.address.village; - const fullAdress = body.address.road + " " + number + ", "+ body.address.postcode+ " "+ city + ", " + body.address.country; - this.setObjectNotExists(vin + ".position.address.displayName", { - type: "state", - common: { - name: "displayName", - role: "indicator", - type: "mixed", - write: false, - read: true - }, - native: {} - }); - this.setState(vin + ".position.address.displayName", fullAdress, true); - Object.keys(body.address).forEach(key => { - this.setObjectNotExists(vin + ".position.address." + key, { - type: "state", - common: { - name: key, - role: "indicator", - type: "mixed", - write: false, - read: true - }, - native: {} - }); - this.setState(vin + ".position.address." + key, body.address[key], true); - - }); - - } catch (error) { - this.log.error(error); - } - } - - }); - - } - } - - } else { - // The state was deleted - // this.log.info(`state ${id} deleted`); - } - } + url = "https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/" + vin + "/auxiliary-heating/" + value; + if (value === "start") { + body = { + spin: "9669", + durationInSeconds: duration, + targetTemperature: { + temperatureValue: 23.0, + unitInCar: "CELSIUS", + }, + }; + const targetTemperatureState = await this.getStateAsync( + vin + ".status.auxiliary-heating.targetTemperature.temperatureValue", + ); + if (targetTemperatureState) { + body.targetTemperature.temperatureValue = targetTemperatureState.val; + } + const remoteTarget = await this.getStateAsync(vin + ".remote.targetTemperatureInCelsius"); + if (remoteTarget) { + body.targetTemperature.temperatureValue = remoteTarget.val; + } + } + } + if (action === "windowheating") { + url = + "https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/" + vin + "/" + value + "-window-heating"; + } + if (action === "access") { + //verify pin first + await this.verifySkodaPin(); + body = { + currentSpin: this.config.pin, + }; + url = "https://mysmob.api.connect.skoda-auto.cz/api/v1/vehicle-access/" + vin + "/" + value; + } + + if (action === "maxChargeCurrent") { + body = { + targetSOCInPercent: value, + }; + url = "https://mysmob.api.connect.skoda-auto.cz/api/v1/charging/" + vin + "/set-charge-limit"; + } + if (action === "targetTemperatureInCelsius") { + body = { + temperatureValue: value, + unitInCar: "CELSIUS", + }; + + url = + "https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/" + vin + "/settings/target-temperature"; + } + if (action === "ventilation") { + url = + "https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/" + vin + "/active-ventilation/" + value; + } + + const method = "POST"; + + this.log.debug(url); + this.log.debug(JSON.stringify(body)); + request( + { + method: method, + url: url, + headers: { + "x-demo-mode": "false", + accept: "application/json", + "content-type": "application/json;charset=utf-8", + "user-agent": this.skodaUserAgent, + "accept-language": "de-de", + authorization: "Bearer " + this.config.atoken, + }, + body: body, + followAllRedirects: true, + json: true, + gzip: true, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + if (resp && resp.statusCode === 401) { + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + this.refreshToken().catch(() => {}); + this.log.error("Refresh Token"); + reject(); + return; + } + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + reject(); + return; + } + try { + this.log.debug(JSON.stringify(body)); + resolve(); + } catch (err) { + this.log.error(err); + reject(); + } + }, + ); + }); + } + async verifySkodaPin() { + await axios({ + method: "post", + url: "https://mysmob.api.connect.skoda-auto.cz/api/v1/spin/verify", + headers: { + "x-demo-mode": "false", + accept: "application/json", + "user-agent": this.skodaUserAgent, + "accept-language": "de-de", + authorization: "Bearer " + this.config.atoken, + }, + data: { currentSpin: this.config.pin }, + }) + .then(async (res) => { + this.log.debug(JSON.stringify(res.data)); + }) + .catch((error) => { + this.log.error(error); + error.response && this.log.error(JSON.stringify(error.response.data)); + }); + } + + async getElliData(type) { + if (this.config.historyLimit == -1) { + this.log.debug("Elli disabled in config"); + return; + } + this.log.debug("Get Elli Data with history limit: " + this.config.historyLimit); + let name = "Seat Elli Data"; + let path = "seatelli"; + if (type === "skodapower") { + name = "Skoda Powerpass Data"; + path = "skodapower"; + } + const header = { + "Content-Type": "application/json", + Accept: "application/json", + "User-Agent": this.userAgent, + "Accept-Language": "de-DE", + Authorization: "Bearer " + this.config.atoken, + }; + await this.extendObjectAsync(path, { + type: "device", + common: { + name: name, + write: false, + }, + native: {}, + }); + const endpoints = [ + "identity/v1/userinfo", + "customer/v1/cars", + "customer/v1/subscriptions", + "customer/v1/rfidcards", + "chargeathome/v1/chargingsessions", + "customer/v1/orders", + "customer/v1/charging/sessions", + "customer/v1/invoices", + "customer/v1/orders", + "customer/v1/subscriber", + ]; + endpoints.forEach((element) => { + const elementArray = element.split("/"); + this.genericRequest( + "https://api.elli.eco/" + element, + header, + path + "." + elementArray[elementArray.length - 1], + [404, 409], + ).catch((hideError, err) => { + if (hideError) { + return; + } + this.log.error(err); + }); + }); + this.genericRequest( + "https://api.elli.eco/customer/v1/charging/records?limit=" + this.config.historyLimit + "&offset=0", + header, + path + ".records", + [404], + ).catch((hideError, err) => { + if (hideError) { + return; + } + this.log.error(err); + }); + + this.genericRequest("https://api.elli.eco/chargeathome/v1/stations", header, path + ".stations", [404], "stations") + .then((body) => { + body.forEach((station) => { + this.genericRequest( + "https://api.elli.eco/chargeathome/v1/stations/" + station.id, + header, + path + ".stations." + station.name, + [404], + ).catch((hideError) => { + if (hideError) { + this.log.debug("Failed to get sessions"); + return; + } + this.log.error("Failed to get sessions"); + }); + this.genericRequest( + "https://api.elli.eco/chargeathome/v1/chargingrecords?station_id=" + + station.id + + "&limit=" + + this.config.historyLimit + + "&offset=0", + header, + path + ".stations." + station.name + ".chargingrecords", + [404], + ).catch((hideError) => { + if (hideError) { + this.log.debug("Failed to get sessions"); + return; + } + this.log.error("Failed to get sessions"); + }); + this.genericRequest( + "https://api.elli.eco/chargeathome/v1/chargingrecords/total-charged?station_id=" + + station.id + + "&limit=" + + this.config.historyLimit + + "&offset=0", + header, + path + ".stations." + station.name + ".chargingrecords.total-charged", + [404], + ).catch((hideError) => { + if (hideError) { + this.log.debug("Failed to get total-charged"); + return; + } + this.log.error("Failed to get total-charged"); + }); + }); + }) + .catch((hideError, err) => { + if (hideError) { + this.log.debug("Failed to get stations"); + this.log.debug(err); + return; + } + this.log.error("Failed to get stations"); + this.log.error(err); + }); + } + + async getWcData(limit) { + if (this.config.historyLimit == -1) { + this.log.debug("WC disabled in config"); + return; + } + if (this.pairedWallbox) { + this.log.debug("Get WC Data with history limit: " + this.config.historyLimit); + //check if latest fetching is minimum 15 minutes ago + if (this.lastWcFetch && this.lastWcFetch + 15 * 60 * 1000 > Date.now()) { + this.log.debug("We Charge data already fetched in last 15 minutes"); + return; + } + + this.lastWcFetch = Date.now(); + if (limit == -1) { + this.log.debug("We Charge disabled in config"); + return; + } + if (!limit) { + limit = 25; + } + const wechargeState = await this.getObjectAsync("wecharge"); + if (wechargeState) { + if (wechargeState.type !== "channel") { + await this.delObjectAsync("wecharge", { recursive: true }); + } + } + await this.extendObject("wecharge", { + type: "channel", + common: { + name: "WeCharge Data", + write: false, + }, + native: {}, + }); + + // Common headers for all requests + const commonHeaders = { + "X-Api-Version": "1", + traceparent: "00-1d499197dd7f48bda994a890fa11fe15-xxxxxxxxxxxxxxxx-01", + Authorization: "Bearer " + this.config.atoken, + Connection: "Keep-Alive", + "Accept-Encoding": "gzip", + "User-Agent": "okhttp/4.12.0", + "X-Cookie": "4olkEx88/5FdWgb95AnxwEwm6Rwp1YOxD6NkObBr", + "X-Debug-Log": "true", + "Content-Type": "application/json", + "Accept-Language": "de-DE", + "X-Brand": this.xbrand, + "X-Platform": "android", + "X-Device-Timezone": "Europe/Berlin", + "X-Sdk-Version": "4.5.4-(2025.18.0)", + "X-Use-BffError-V2": "true", + "X-Device-Manufacturer": "google", + "X-Device-Name": "Pixel 4a", + "X-Device-OS-Name": "13", + "X-Device-OS-Version": "33", + }; + + const requests = [ + { + method: "GET", + url: "https://prod.emea.mobile.charging.cariad.digital/headless/subscription", + headers: { + ...commonHeaders, + Host: "prod.emea.mobile.charging.cariad.digital", + }, + path: "wecharge.subscription", + }, + { + method: "GET", + url: "https://prod.emea.mobile.charging.cariad.digital/headless/marketConfiguration?fallbackCountry=DE", + headers: commonHeaders, + path: "wecharge.configuration", + }, + { + method: "GET", + url: "https://prod.emea.mobile.charging.cariad.digital/charging_history/public", + headers: { ...commonHeaders, "X-Api-Version": "2" }, + path: "wecharge.history.public", + }, + { + method: "POST", + url: "https://prod.emea.mobile.charging.cariad.digital/charging_history/home", + headers: commonHeaders, + data: { + filteredBy: {}, + retrieveFilters: true, + }, + path: "wecharge.history.home", + }, + { + method: "GET", + url: "https://prod.emea.mobile.charging.cariad.digital/charging_stations", + headers: { ...commonHeaders, "X-Api-Version": "3" }, + path: "wecharge.stations", + }, + ]; + + for (const request of requests) { + await axios(request) + .then(async (response) => { + let options = { forceIndex: true }; + if (response.data.items) { + //remove all items with type 'section' + response.data.items = response.data.items.filter((item) => item.type !== "section"); + //remove all items with type 'header' + response.data.items = response.data.items.filter((item) => item.type !== "header"); + + //put type 'month' in seperate array + response.data.months = response.data.items.filter((item) => item.type === "month"); + //remove all items with type 'month' + response.data.items = response.data.items.filter((item) => item.type !== "month"); + response.data.years = response.data.items.filter((item) => item.type === "year"); + //remove all items with type 'year' + response.data.items = response.data.items.filter((item) => item.type !== "year"); + } + if (request.path === "wecharge.stations") { + options = { preferedArrayName: "id", preferedArrayDesc: "name" }; + } + await this.json2iob.parse(request.path, response.data, options); + if (request.path === "wecharge.stations") { + for (const stations of response.data.result) { + this.extendObject("wecharge.stations." + stations.id + ".startCharging", { + type: "state", + common: { + name: "Start Charging", + type: "boolean", + role: "button", + read: false, + write: true, + }, + native: {}, + }); + } + } + }) + .catch((error) => { + this.log.error(`Failed to get data for ${request.path}`); + this.log.error(error); + error.response && this.log.error(JSON.stringify(error.response.data)); + }); + } + } + } + genericRequest(url, header, path, codesToIgnoreArray, selector1, selector2) { + //eslint-disable-next-line + return new Promise(async (resolve, reject) => { + header["If-None-Match"] = this.etags[url] || ""; + request.get( + { + url: url, + headers: header, + followAllRedirects: true, + gzip: true, + json: true, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + if (resp && resp.statusCode && codesToIgnoreArray.includes(resp.statusCode)) { + err && this.log.debug(err); + resp && this.log.debug(resp.statusCode.toString()); + body && this.log.debug(JSON.stringify(body)); + reject(true, err); + return; + } + + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + reject(false, err); + return; + } + this.log.debug(url); + this.log.debug(JSON.stringify(body)); + this.etags[url] = resp.headers.etag; + if (resp.statusCode === 304) { + this.log.debug("304 No values updated"); + resolve(); + return; + } + try { + if (selector1) { + body = body[selector1]; + if (selector2) { + body = body[selector2]; + } + } + if (this.config.rawJson) { + this.extendObjectAsync(path + "rawJson", { + type: "state", + common: { + name: path + "rawJson", + role: "state", + type: "json", + write: false, + read: true, + }, + native: {}, + }) + .then(() => { + this.setState(path + "rawJson", JSON.stringify(body), true); + }) + .catch((error) => { + this.log.error(error); + }); + } + const preferedArrayName = null; + let forceIndex = null; + if ( + path.indexOf("chargingsessions") !== -1 || + path.indexOf("chargingrecords") !== -1 || + path.indexOf("records") !== -1 || + path.indexOf("sessions") !== -1 + ) { + forceIndex = true; + } + + this.extractKeys(this, path, body, preferedArrayName, forceIndex); + resolve(body); + } catch (err) { + this.log.error(err); + reject(); + } + }, + ); + }); + } + setIdRemote(vin, action, value, bodyContent) { + //eslint-disable-next-line + return new Promise(async (resolve, reject) => { + const pre = this.name + "." + this.instance; + let body = bodyContent || { spin: this.config.pin }; + if (action === "climatisation" && value === "start") { + const climateStates = await this.getStatesAsync(pre + "." + vin + ".status.climatisationSettings.*"); + body = {}; + const allIds = Object.keys(climateStates); + allIds.forEach((keyName) => { + const key = keyName.split(".").splice(-1)[0]; + if (key.indexOf("Timestamp") === -1) { + body[key] = climateStates[keyName].val; + } + }); + if (body.targetTemperature_C) { + body.targetTemperature = body.targetTemperature_C; + body.targetTemperatureUnit = "celsius"; + } + // body = JSON.stringify(body); + } + if (action === "honkandflash" || action === "flash") { + const latState = await this.getStateAsync(vin + ".parkingposition.lat"); + const longState = await this.getStateAsync(vin + ".parkingposition.lon"); + if (!latState || !longState) { + this.log.error("No parking position found"); + reject(); + return; + } + body = { + duration_s: 15, + mode: action, + userPosition: { + latitude: latState.val, + longitude: longState.val, + }, + }; + action = "honkandflash"; + } + let method = "POST"; + if (value === "settings") { + method = "PUT"; + } + let url = "https://emea.bff.cariad.digital/vehicle/v1/vehicles/" + vin + "/" + action; + if (value) { + url += "/" + value; + } + this.log.debug(url); + this.log.debug(JSON.stringify(body)); + request( + { + method: method, + url: url, + + headers: { + "content-type": "application/json", + accept: "*/*", + "accept-language": "de-de", + "user-agent": this.userAgent, + "content-version": "1", + "x-newrelic-id": "VgAEWV9QDRAEXFlRAAYPUA==", + authorization: "Bearer " + this.config.atoken, + }, + body: body, + followAllRedirects: true, + json: true, + gzip: true, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + if (resp && resp.statusCode === 401) { + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + if (this.config.type === "audietron") { + this.refreshTokenv2().catch(() => {}); + } else { + this.refreshIDToken().catch(() => {}); + } + this.log.error("Refresh Token"); + reject(); + return; + } + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + reject(); + return; + } + try { + this.log.info(JSON.stringify(body)); + resolve(); + } catch (err) { + this.log.error(err); + reject(); + } + }, + ); + }); + } + refreshTokenv2() { + return new Promise((resolve, reject) => { + this.log.debug("Token Refresh started"); + const body = { + client_id: this.clientId, + grant_type: "refresh_token", + refresh_token: this.config.rtoken, + response_type: "token id_token", + }; + const headers = { + accept: "application/json", + "content-type": "application/x-www-form-urlencoded; charset=utf-8", + "accept-charset": "utf-8", + "x-qmauth": this.getQmauth(), + "accept-language": "de-de", + "user-agent": "myAudi-Android/4.13.0 (Build 800236847.2111261819) Android/11", + }; + request( + { + method: "POST", + url: "https://emea.bff.cariad.digital/login/v1/idk/token", + headers: headers, + followAllRedirects: true, + gzip: true, + json: true, + body: qs.stringify(body), + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + this.log.error("Failed refresh token. restart adapter in 10min"); + setTimeout(() => { + this.log.error("restart adapter"); + this.restart(); + }, 10 * 60 * 1000); + reject(); + return; + } + try { + this.log.debug("Token Refresh successful"); + this.config.atoken = body.access_token; + this.config.rtoken = body.refresh_token; + + resolve(); + } catch (err) { + this.log.error(err); + reject(); + } + }, + ); + }); + } + + async refreshIDToken() { + this.log.debug("Token Refresh started"); + + try { + const refreshHeaders = { + "Accept-Encoding": "gzip, deflate, br", + "Connection": "keep-alive", + "Content-Type": "application/x-www-form-urlencoded", + "User-Agent": this.userAgent, + "Accept-Language": "de-de", + }; + + if (this.config.type === "id" && this.androidPackageName) { + refreshHeaders["x-android-package-name"] = this.androidPackageName; + } + + const refreshBody = { + grant_type: "refresh_token", + refresh_token: this.config.rtoken, + client_id: this.clientId, + }; + + // Use BFF token endpoint for refresh (same as Python) + const response = await axios({ + method: "post", + url: "https://emea.bff.cariad.digital/login/v1/idk/token", + headers: refreshHeaders, + data: new URLSearchParams(refreshBody).toString(), + }); + + this.log.debug("Token Refresh successful"); + const tokens = response.data; + + // Store refreshed tokens (handle both camelCase and snake_case) + this.config.atoken = tokens.access_token || tokens.accessToken; + this.config.rtoken = tokens.refresh_token || tokens.refreshToken; + + return Promise.resolve(); + } catch (error) { + if (error.response) { + this.log.error("Status: " + error.response.status); + this.log.error("Response: " + JSON.stringify(error.response.data)); + } else { + this.log.error(error.message); + } + this.log.error("Failed refresh token. Relogin"); + + // Reset login parameters because of wecharge + this.type = "Id"; + this.clientId = "a24fba63-34b3-4d43-b181-942111e6bda8@apps_vw-dilab_com"; + this.scope = "openid profile badge cars dealers birthdate vin"; + this.redirect = "weconnect://authenticated"; + this.xrequest = "com.volkswagen.weconnect"; + this.responseType = "code id_token token"; + + setTimeout(() => { + this.log.error("restart adapter in 10min"); + this.restart(); + }, 10 * 60 * 1000); + + return Promise.reject(error); + } + } + + async refreshSkodaEToken() { + this.log.debug("Token Refresh started"); + await axios({ + method: "post", + url: "https://mysmob.api.connect.skoda-auto.cz/api/v1/authentication/refresh-token?tokenType=CONNECT", + headers: { + accept: "*/*", + "content-type": "application/json", + "user-agent": this.skodaUserAgent, + "accept-language": "de-de", + }, + data: { + token: this.config.rtoken, + }, + }) + .then((res) => { + this.log.debug("Token Refresh successful"); + this.config.atoken = res.data.accessToken; + this.config.rtoken = res.data.refreshToken; + this.log.debug("Start new mqtt connection"); + this.connectMqtt(); + }) + .catch((error) => { + this.log.error("Failed refresh token. Relogin in 10min"); + this.reloginTimeout = this.setTimeout(() => { + this.login(); + }, 10 * 60 * 1000); + this.log.error(error); + }); + } + async refreshSeatCupraToken() { + this.log.debug("Token Refresh started"); + axios({ + method: "post", + maxBodyLength: Infinity, + url: "https://ola.prod.code.seat.cloud.vwgroup.com/authorization/api/v1/token", + headers: { + Accept: "*/*", + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + Connection: "keep-alive", + "User-Agent": "SEATApp/2.5.0 (com.seat.myseat.ola; build:202410171614; iOS 15.8.3) Alamofire/5.7.0 Mobile", + "Accept-Language": "de-DE,de;q=0.9", + }, + data: { + client_id: this.clientId, + grant_type: "refresh_token", + refresh_token: this.config.rtoken, + }, + }) + .then((response) => { + this.log.debug("Token Refresh successful"); + this.config.atoken = response.data.access_token; + this.config.rtoken = response.data.refresh_token; + }) + .catch((error) => { + this.log.error("Failed refresh token. Relogin"); + this.log.error(error); + error.response && this.log.error(error.response.status.toString()); + error.response && this.log.error(JSON.stringify(error.response.data)); + setTimeout(() => { + this.log.error("restart adapter in 10min"); + this.restart(); + }, 10 * 60 * 1000); + }); + } + getVehicleData(vin) { + return new Promise((resolve, reject) => { + if (this.config.type === "go") { + resolve(); + return; + } + let accept = + "application/vnd.vwg.mbb.vehicleDataDetail_v2_1_0+json, application/vnd.vwg.mbb.genericError_v1_0_2+json"; + let url = this.replaceVarInUrl( + "$homeregion/fs-car/vehicleMgmt/vehicledata/v2/$type/$country/vehicles/$vin/", + vin, + ); + if ( + this.config.type !== "vw" && + this.config.type !== "vwv2" && + this.config.type !== "audi" && + this.config.type !== "id" && + this.config.type !== "seat" && + this.config.type !== "skoda" + ) { + url = this.replaceVarInUrl( + "https://msg.volkswagen.de/fs-car/promoter/portfolio/v1/$type/$country/vehicle/$vin/carportdata", + vin, + ); + accept = "application/json"; + } + const atoken = this.config.vwatoken; + + request.get( + { + url: url, + headers: { + "User-Agent": this.userAgent, + "X-App-Version": this.xappversion, + "X-App-Name": this.xappname, + "X-Market": "de_DE", + Authorization: "Bearer " + atoken, + "If-None-Match": this.etags[url] || "", + Accept: accept, + }, + followAllRedirects: true, + gzip: true, + json: true, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + if (resp && resp.statusCode === 429) { + this.log.error( + "Too many requests. Please turn on your car to send new requests. Maybe force update/update erzwingen is too often.", + ); + } + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + reject(); + return; + } + try { + this.log.debug(JSON.stringify(body)); + let result = body.vehicleData; + if (!result) { + result = body.vehicleDataDetail; + } + if (resp) { + this.etags[url] = resp.headers.etag; + if (resp.statusCode === 304) { + this.log.debug("304 No values updated"); + resolve(); + return; + } + } + if (result && result.carportData && result.carportData.modelName) { + this.updateName(vin, result.carportData.modelName); + } + + this.extractKeys(this, vin + ".general", result); + + resolve(); + } catch (err) { + this.log.error(err); + reject(); + } + }, + ); + }); + } + + getVehicleRights(vin) { + return new Promise((resolve, reject) => { + if (this.config.type === "go" || !this.config.rights) { + resolve(); + return; + } + let url = "https://mal-1a.prd.ece.vwg-connect.com/api/rolesrights/operationlist/v3/vehicles/" + vin; + if (this.config.type === "vw" || this.config.type === "vwv2") { + url += "/users/" + this.config.identifier; + } + request.get( + { + url: url, + qs: { + scope: "All", + }, + headers: { + "User-Agent": this.userAgent, + "X-App-Version": this.xappversion, + "X-App-Name": this.xappname, + Authorization: "Bearer " + this.config.vwatoken, + Accept: + "application/json, application/vnd.vwg.mbb.operationList_v3_0_2+xml, application/vnd.vwg.mbb.genericError_v1_0_2+xml", + }, + followAllRedirects: true, + gzip: true, + json: true, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + if (resp && resp.statusCode === 429) { + this.log.error( + "Too many requests. Please turn on your car to send new requests. Maybe force update/update erzwingen is too often.", + ); + } + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + reject(); + return; + } + try { + const adapter = this; + traverse(body.operationList).forEach(function (value) { + if (this.path.length > 0 && this.isLeaf) { + const modPath = this.path; + this.path.forEach((pathElement, pathIndex) => { + if (!isNaN(parseInt(pathElement))) { + let stringPathIndex = parseInt(pathElement) + 1 + ""; + while (stringPathIndex.length < 2) stringPathIndex = "0" + stringPathIndex; + const key = this.path[pathIndex - 1] + stringPathIndex; + const parentIndex = modPath.indexOf(pathElement) - 1; + modPath[parentIndex] = key; + modPath.splice(parentIndex + 1, 1); + } + }); + if (modPath[modPath.length - 1] !== "$") { + adapter + .setObjectNotExistsAsync(vin + ".rights." + modPath.join("."), { + type: "state", + common: { + name: this.key, + role: "indicator", + type: "mixed", + write: false, + read: true, + }, + native: {}, + }) + .then(() => { + if (typeof value === "object") { + value = JSON.stringify(value); + } + adapter.setState(vin + ".rights." + modPath.join("."), value || this.node, true); + }) + .catch((error) => { + adapter.log.error(error); + }); + } + } + }); + + resolve(); + } catch (err) { + this.log.error(err); + reject(); + } + }, + ); + }); + } + + requestStatusUpdate(vin) { + return new Promise((resolve, reject) => { + try { + let method = "POST"; + let url = this.replaceVarInUrl("$homeregion/fs-car/bs/vsr/v1/$type/$country/vehicles/$vin/requests", vin); + + let accept = "application/json"; + // if (this.config.type === "audi") { + // url = this.replaceVarInUrl("https://mal-3a.prd.eu.dp.vwg-connect.com/api/bs/vsr/v1/vehicles/$vin/requests", vin); + // } + if (this.config.type === "vw") { + accept = + "application/vnd.vwg.mbb.VehicleStatusReport_v1_0_0+json, application/vnd.vwg.mbb.climater_v1_0_0+json, application/vnd.vwg.mbb.carfinderservice_v1_0_0+json, application/vnd.volkswagenag.com-error-v1+json, application/vnd.vwg.mbb.genericError_v1_0_2+json"; + } + if (this.config.type === "vwv2") { + method = "GET"; + url = this.replaceVarInUrl("$homeregion/fs-car/vehicleMgmt/vehicledata/v2/$type/$country/vehicles/$vin", vin); + accept = + " application/vnd.vwg.mbb.vehicleDataDetail_v2_1_0+json, application/vnd.vwg.mbb.genericError_v1_0_2+json"; + } + this.log.debug("Request update " + url); + request( + { + method: method, + url: url, + headers: { + "User-Agent": this.userAgent, + "X-App-Version": this.xappversion, + "X-App-Name": this.xappname, + Authorization: "Bearer " + this.config.vwatoken, + "Accept-charset": "UTF-8", + Accept: accept, + }, + followAllRedirects: true, + gzip: true, + json: true, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + this.log.error(vin); + if (resp && resp.statusCode === 429) { + this.log.error( + "Too many requests. Please turn on your car to send new requests. Maybe force update/update erzwingen is too often.", + ); + } + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + reject(); + return; + } + try { + this.log.debug(JSON.stringify(body)); + resolve(); + } catch (err) { + this.log.error("Request update failed: " + url); + this.log.error(vin); + this.log.error(err); + reject(); + } + }, + ); + } catch (err) { + this.log.error(err); + reject(); + } + }); + } + + getVehicleStatus(vin, url, path, element, element2, element3, element4, tripType) { + return new Promise((resolve, reject) => { + url = this.replaceVarInUrl(url, vin, tripType); + if (path === "tripdata") { + if (this.tripsActive == false) { + resolve(); + return; + } + } + let accept = "application/json"; + if (this.config.type === "vw" || this.config.type === "vwv2") { + accept = + "application/vnd.vwg.mbb.VehicleStatusReport_v1_0_0+json, application/vnd.vwg.mbb.climater_v1_0_0+json, application/vnd.vwg.mbb.carfinderservice_v1_0_0+json, application/vnd.volkswagenag.com-error-v1+json, application/vnd.vwg.mbb.genericError_v1_0_2+json, */*"; + if (this.homeRegion[vin] === "https://msg.volkswagen.de") { + accept += ", application/json"; + } + } + request.get( + { + url: url, + headers: { + "User-Agent": this.userAgent, + "X-App-Version": this.xappversion, + "X-App-Name": this.xappname, + "If-None-Match": this.etags[url] || "", + Authorization: "Bearer " + this.config.vwatoken, + "Accept-charset": "UTF-8", + Accept: accept, + }, + followAllRedirects: true, + gzip: true, + json: true, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + if ( + (resp && resp.statusCode === 403) || + (resp && resp.statusCode === 502) || + (resp && resp.statusCode === 406) || + (resp && resp.statusCode === 500) + ) { + body && this.log.debug(JSON.stringify(body)); + resolve(); + return; + } else if (resp && resp.statusCode === 401) { + this.log.error(vin); + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + this.log.error("Refresh Token in 10min"); + if (!this.refreshTokenTimeout) { + this.refreshTokenTimeout = setTimeout(() => { + this.refreshTokenTimeout = null; + this.refreshToken(true).catch(() => { + this.log.error("Refresh Token was not successful"); + }); + }, 10 * 60 * 1000); + } + reject(); + return; + } else { + if (resp && resp.statusCode === 429) { + this.log.error( + "Too many requests. Please turn on your car to send new requests. Maybe force update/update erzwingen is too often.", + ); + } + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + reject(); + return; + } + } + try { + this.log.debug(JSON.stringify(body)); + if (resp) { + this.etags[url] = resp.headers.etag; + if (resp.statusCode === 304) { + this.log.debug("304 No values updated"); + resolve(); + return; + } + } + if (path === "position") { + if (body && body.storedPositionResponse && body.storedPositionResponse.parkingTimeUTC) { + body.storedPositionResponse.position.parkingTimeUTC = body.storedPositionResponse.parkingTimeUTC; + } + this.setIsCarMoving(vin, resp.statusCode === 204); + } + + if (body === undefined || body === "" || body.error) { + if (body && body.error && body.error.description.indexOf("Token expired") !== -1) { + this.log.error("Error response try to refresh token " + path); + this.log.error(JSON.stringify(body)); + this.log.error("Refresh Token in 10min"); + if (!this.refreshTokenTimeout) { + this.refreshTokenTimeout = setTimeout(() => { + this.refreshTokenTimeout = null; + this.refreshToken(true).catch(() => { + this.log.error("Refresh Token was not successful"); + }); + }, 10 * 60 * 1000); + } + } else { + this.log.debug("Not able to get " + path); + } + this.log.debug(JSON.stringify(body)); + reject(); + return; + } + + const adapter = this; + + let result = body; + if (result === "") { + resolve(); + return; + } + if (result) { + if (element && result[element]) { + result = result[element]; + } + if (element2 && result[element2]) { + result = result[element2]; + } + if (element3 && result[element3]) { + result = result[element3]; + } + if (element4 && result[element4]) { + result = result[element4]; + } + const isStatusData = path === "status"; + const isTripData = path === "tripdata"; + + if (isTripData) { + if (this.tripsActive == false) { + resolve(); + return; + } + // result.tripData = result.tripData.reverse(); + result.tripData.sort((a, b) => { + return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(); + }); + if (this.config.numberOfTrips > 0) + result.tripData = result.tripData.slice(0, this.config.numberOfTrips); + this.extendObjectAsync(vin + ".tripdata" + tripType + ".rawJson", { + type: "state", + common: { + name: "Raw Json", + role: "indicator", + type: "mixed", + write: false, + read: true, + }, + native: {}, + }) + .then(() => { + this.setState(vin + ".tripdata" + tripType + ".rawJson", JSON.stringify(result.tripData), true); + }) + .catch((error) => { + this.log.error(error); + }); + + this.extendObjectAsync(vin + ".tripdata" + tripType + ".lastTrip", { + type: "state", + common: { + name: "indexOfOldestTrip", + role: "indicator", + type: "mixed", + write: false, + read: true, + }, + native: {}, + }) + .then(() => { + this.setState(vin + ".tripdata" + tripType + ".lastTrip", result.tripData.length, true); + }) + .catch((error) => { + this.log.error(error); + }); + + this.extractKeys(this, vin + ".tripdata" + tripType, result, null, true); + + resolve(); + return; + } + + let statusKeys = null; + if (isStatusData) { + statusKeys = this.getStatusKeys(result); + } + const tripKeys = null; + + traverse(result).forEach(function (value) { + const modPath = this.path.slice(); + let dataId = null; + let dataIndex = -1; + let fieldId = null; + let fieldUnit = null; + let isNumberNode = false; + let skipNode = false; + this.path.forEach((pathElement, pathIndex) => { + if (isNaN(parseInt(pathElement))) { + isNumberNode = false; + } else { + isNumberNode = true; + let key; + if (isStatusData && this.path[pathIndex - 1] === "data") { + dataIndex = parseInt(pathElement); + dataId = statusKeys[dataIndex].dataId; + key = "_" + dataId; + } else if (isStatusData && this.path[pathIndex - 1] === "field") { + if (dataIndex >= 0) { + fieldId = statusKeys[dataIndex].fieldIds[parseInt(pathElement)].id; + key = "_" + fieldId; + if (this.key == "value" && statusKeys[dataIndex].fieldIds[parseInt(pathElement)].unit) { + fieldUnit = statusKeys[dataIndex].fieldIds[parseInt(pathElement)].unit; + } + } else { + adapter.log.error("no data entry found for field (path = " + this.path.join(".")); + key = parseInt(pathElement) + 1 + ""; + } + } else if (isTripData && this.path[pathIndex - 1]) { + const tripKey = tripKeys[parseInt(pathElement)]; + if (tripKey === null) { + skipNode = true; + } else { + key = "_" + tripKeys[parseInt(pathElement)]; + } + } else { + key = parseInt(pathElement) + 1 + ""; + while (key.length < 2) key = "0" + key; + } + if (!skipNode) { + const parentIndex = modPath.indexOf(pathElement) - 1; + modPath[parentIndex] = this.path[pathIndex - 1] + key; + modPath.splice(parentIndex + 1, 1); + } + } + }); + if (!skipNode) { + const newPath = vin + "." + path + "." + modPath.join("."); + if (this.path.length > 0 && this.isLeaf) { + value = value || this.node; + if (!isNaN(Number(value)) && Number(value) === parseFloat(value)) { + value = Number(value); + } + let name = this.key; + if (typeof this.key === "number") { + name = this.key.toString(); + } + adapter + .setObjectNotExistsAsync(newPath, { + type: "state", + common: { + name: name, + role: "indicator", + type: typeof value, + unit: fieldUnit, + write: false, + read: true, + }, + native: {}, + }) + .then(() => { + if (typeof value === "object") { + value = JSON.stringify(value); + } + adapter.setState(newPath, value, true); + }) + .catch((error) => { + adapter.log.error(error); + }); + //if (isStatusData && newPath.endsWith(".outdoorTemperature.content")) { + // setOutsideTemperature(vin, value); + //} + if (isStatusData && this.key == "value") { + // Audi and Skoda have different (shorter) dataId + if ((dataId == "0x030104FFFF" || dataId == "0x0301FFFFFF") && fieldId == "0x0301040001") { + adapter.setIsCarLocked(vin, value == 2); + } + if ((dataId == "0x030102FFFF" || dataId == "0x0301FFFFFF") && fieldId == "0x0301020001") { + adapter.setOutsideTemperature(vin, value); + } + adapter.updateUnit(newPath, fieldUnit); + } + } else if (isStatusData && isNumberNode) { + let text = null; + if (this.node.textId) { + text = this.node.textId; + } + adapter.setObjectNotExists(newPath, { + type: "channel", + common: { + name: text, + role: "indicator", + type: "mixed", + write: false, + read: true, + }, + native: {}, + }); + adapter.updateName(newPath, text); + } else if (isTripData && isNumberNode) { + let text = null; + if (this.node.timestamp) { + text = this.node.timestamp; + } + adapter.setObjectNotExists(newPath, { + type: "channel", + common: { + name: text, + role: "indicator", + type: "mixed", + write: false, + read: true, + }, + native: {}, + }); + adapter.updateName(newPath, text); + } + } + }); + resolve(); + } else { + this.log.error("Cannot find vehicle data " + path); + this.log.error(JSON.stringify(body)); + reject(); + } + } catch (err) { + this.log.error(err); + this.log.error(err.stack); + reject(); + } + }, + ); + }); + } + async setOtherStatesInChannelNull(channel, ts) { + const states = await this.getStatesAsync(channel + ".*"); + const allIds = Object.keys(states); + for (const keyName of allIds) { + if (states[keyName] && states[keyName].ts < ts) { + await this.setStateAsync(keyName, null, true); + } + } + } + + async setIsCarMoving(vin, isMoving, statusFolder) { + if (!statusFolder) { + statusFolder = ""; + } + this.log.debug("Set is car moving to " + isMoving); + await this.extendObjectAsync(vin + statusFolder + ".position.isMoving", { + type: "state", + common: { + name: "is car moving", + role: "indicator", + type: "boolean", + write: false, + read: true, + }, + native: {}, + }); + await this.setStateAsync(vin + statusFolder + ".position.isMoving", isMoving, true); + } + + async setIsCarLocked(vin, value) { + await this.extendObjectAsync(vin + ".status.isCarLocked", { + type: "state", + common: { + name: "is car locked", + role: "indicator", + type: "boolean", + write: false, + read: true, + }, + native: {}, + }); + this.setState(vin + ".status.isCarLocked", value, true); + } + + async setOutsideTemperature(vin, value) { + await this.extendObjectAsync(vin + ".status.outsideTemperature", { + type: "state", + common: { + name: "outside temperature", + role: "value.temperature", + type: "number", + unit: "°C", + write: false, + read: true, + }, + native: {}, + }); + this.setState(vin + ".status.outsideTemperature", Math.round(value - 2731.5) / 10.0, true); + } + + getStatusKeys(statusJson) { + const adapter = this; + let result = null; + if (statusJson && statusJson.data) { + if (Array.isArray(statusJson.data)) { + result = new Array(statusJson.data.length); + statusJson.data.forEach(function (dataValue, dataIndex) { + if (dataValue && dataValue.id) { + if (dataValue.field && Array.isArray(dataValue.field)) { + const newList = new Array(dataValue.field.length); + dataValue.field.forEach(function (fieldValue, fieldIndex) { + if (fieldValue && fieldValue.id) { + newList[fieldIndex] = { id: fieldValue.id, unit: fieldValue.unit }; + } else { + adapter.log.warn("status[" + dataIndex + "," + fieldIndex + "] has no id"); + adapter.log.debug(JSON.stringify(fieldValue)); + } + }); + result[dataIndex] = { dataId: dataValue.id, fieldIds: newList }; + } else { + adapter.log.warn("status[" + dataIndex + "] has no fields/is not an array"); + adapter.log.debug(JSON.stringify(dataValue)); + } + } else { + adapter.log.warn("status[" + dataIndex + "] has no id"); + adapter.log.debug(JSON.stringify(dataValue)); + } + }); + } else { + adapter.log.warn("status is not an array"); + adapter.log.debug(JSON.stringify(statusJson.data)); + } + } else { + adapter.log.warn("status data without status field"); + adapter.log.debug(JSON.stringify(statusJson)); + } + adapter.log.debug(JSON.stringify(result)); + return result; + } + + updateUnit(pathString, unit) { + const adapter = this; + this.getObject(pathString, function (err, obj) { + if (err) adapter.log.error('Error "' + err + '" reading object ' + pathString + " for unit"); + else { + // @ts-ignore + if (obj && obj.common && obj.common.unit !== unit) { + adapter.extendObject(pathString, { + type: "state", + common: { + unit: unit, + }, + }); + } + } + }); + } + + updateName(pathString, name) { + const adapter = this; + this.getObject(pathString, function (err, obj) { + if (err) adapter.log.error('Error "' + err + '" reading object ' + pathString + " for name"); + else { + if (obj && obj.common && obj.common.name !== name) { + adapter.extendObject(pathString, { + type: "channel", + common: { + name: name, + }, + }); + } + } + }); + } + + setVehicleStatus(vin, url, body, contentType, secToken) { + return new Promise((resolve, reject) => { + url = this.replaceVarInUrl(url, vin); + this.log.debug(JSON.stringify(body)); + this.log.debug(contentType); + const headers = { + "User-Agent": this.userAgent, + "X-App-Version": this.xappversion, + "X-App-Name": this.xappname, + Authorization: "Bearer " + this.config.vwatoken, + "Accept-charset": "UTF-8", + "Content-Type": contentType, + Accept: + "application/json, application/vnd.vwg.mbb.ChargerAction_v1_0_0+xml,application/vnd.volkswagenag.com-error-v1+xml,application/vnd.vwg.mbb.genericError_v1_0_2+xml, application/vnd.vwg.mbb.RemoteStandheizung_v2_0_0+xml, application/vnd.vwg.mbb.genericError_v1_0_2+xml,application/vnd.vwg.mbb.RemoteLockUnlock_v1_0_0+xml,*/*", + }; + + if (secToken) { + headers["x-mbbSecToken"] = secToken; + headers["X-SecurityToken"] = secToken; + } + + request.post( + { + url: url, + headers: headers, + body: body, + followAllRedirects: true, + gzip: true, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(body); + reject(); + return; + } + try { + this.log.debug(JSON.stringify(body)); + if (body.indexOf("") !== -1) { + this.log.error("Error response try to refresh token " + url); + this.log.error(JSON.stringify(body)); + this.refreshToken(true).catch(() => { + this.log.error("Refresh Token was not successful"); + }); + reject(); + return; + } + resolve(); + this.log.info(body); + } catch (err) { + this.log.error(err); + this.log.error(err.stack); + reject(); + } + }, + ); + }); + } + setVehicleStatusv2(vin, url, body, contentType, secToken) { + return new Promise((resolve, reject) => { + url = this.replaceVarInUrl(url, vin); + this.log.debug(JSON.stringify(body)); + this.log.debug(contentType); + const headers = { + "User-Agent": this.userAgent, + "X-App-Version": this.xappversion, + "X-App-Name": this.xappname, + Authorization: "Bearer " + this.config.vwatoken, + "Accept-charset": "UTF-8", + "Content-Type": contentType, + Accept: + "application/json, application/vnd.vwg.mbb.ChargerAction_v1_0_0+xml,application/vnd.volkswagenag.com-error-v1+xml,application/vnd.vwg.mbb.genericError_v1_0_2+xml, application/vnd.vwg.mbb.RemoteStandheizung_v2_0_0+xml, application/vnd.vwg.mbb.genericError_v1_0_2+xml,application/vnd.vwg.mbb.RemoteLockUnlock_v1_0_0+xml,*/*", + }; + if (secToken) { + headers["x-mbbSecToken"] = secToken; + } + + request.post( + { + url: url, + headers: headers, + body: body, + followAllRedirects: true, + gzip: true, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + reject(); + return; + } + try { + this.log.debug(JSON.stringify(body)); + if (body.indexOf("") !== -1) { + this.log.error("Error response try to refresh token " + url); + this.log.error(JSON.stringify(body)); + this.refreshToken(true).catch(() => { + this.log.error("Refresh Token was not successful"); + }); + reject(); + return; + } + this.log.info(body); + } catch (err) { + this.log.error(err); + this.log.error(err.stack); + reject(); + } + }, + ); + }); + } + requestSecToken(vin, service) { + return new Promise((resolve, reject) => { + let url = + "https://mal-1a.prd.ece.vwg-connect.com/api/rolesrights/authorization/v2/vehicles/" + + vin + + "/services/" + + service + + "/security-pin-auth-requested"; + if (this.homeRegionSetter[vin]) { + url = url.replace("https://mal-1a.prd.ece.vwg-connect.com", this.homeRegionSetter[vin]); + } + this.log.debug(url); + request.get( + { + url: url, + headers: { + "user-agent": this.userAgent, + "X-App-version": this.xappversion, + "X-App-name": this.xappname, + authorization: "Bearer " + this.config.vwatoken, + accept: "application/json", + }, + followAllRedirects: true, + json: true, + gzip: true, + }, + async (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + err && this.log.error(err); + resp && this.log.error(resp.statusCode.toString()); + body && this.log.error(JSON.stringify(body)); + reject(); + return; + } + try { + if (body.error) { + this.log.error(JSON.stringify(body.error)); + reject(); + } + this.log.debug(JSON.stringify(body)); + if (body.securityPinAuthInfo) { + const secToken = body.securityPinAuthInfo.securityToken; + const challenge = body.securityPinAuthInfo.securityPinTransmission.challenge; + const securPin = await this.generateSecurPin(challenge); + const rBody = { + securityPinAuthentication: { + securityPin: { + challenge: challenge, + securityPinHash: securPin, + }, + securityToken: secToken, + }, + }; + let url = + "https://mal-1a.prd.ece.vwg-connect.com/api/rolesrights/authorization/v2/security-pin-auth-completed"; + if (this.homeRegionSetter[vin]) { + url = url.replace("https://mal-1a.prd.ece.vwg-connect.com", this.homeRegionSetter[vin]); + } + request.post( + { + url: url, + headers: { + "user-agent": this.userAgent, + "Content-Type": "application/json", + "X-App-version": this.xappversion, + "X-App-name": this.xappname, + authorization: "Bearer " + this.config.vwatoken, + Accept: "application/json", + }, + body: rBody, + gzip: true, + json: true, + followAllRedirects: true, + }, + (err, resp, body) => { + if (err || (resp && resp.statusCode >= 400)) { + this.log.error("Failing to get sec token."); + err && this.log.error(err); + body && this.log.error(JSON.stringify(body)); + resp && this.log.error(resp.statusCode.toString()); + reject(); + return; + } + try { + this.log.debug(JSON.stringify(body)); + if (body.securityToken) { + resolve(body.securityToken); + } else { + this.log.error("No Security token found"); + this.log.error(JSON.stringify(body)); + reject(); + } + } catch (err) { + this.log.error(err); + reject(); + } + }, + ); + } else { + this.log.error("No Security information found"); + this.log.error(JSON.stringify(body)); + reject(); + } + } catch (err) { + this.log.error(err); + reject(); + } + }, + ); + }); + } + generateSecurPin(challenge) { + return new Promise((resolve, reject) => { + if (!this.config.pin) { + this.log.error("Please Enter your S-Pin in the Instance Options"); + reject(); + return; + } + const pin = this.toByteArray(this.config.pin); + + const byteChallenge = this.toByteArray(challenge); + const webcrypto = new Crypto(); + const concat = new Int8Array(pin.concat(byteChallenge)); + webcrypto.subtle + .digest("SHA-512", concat) + .then((digest) => { + const utf8Array = new Int8Array(digest); + resolve(this.toHexString(utf8Array)); + }) + .catch((error) => { + this.log.error(error); + }); + }); + } + getCodeChallenge() { + let hash = ""; + let result = ""; + const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + result = ""; + for (let i = 64; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)]; + result = Buffer.from(result).toString("base64"); + result = result.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); + hash = crypto.createHash("sha256").update(result).digest("base64"); + hash = hash.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); + + return [result, hash]; + } + getCodeChallengev2() { + let hash = ""; + let result = ""; + const chars = "0123456789abcdef"; + result = ""; + for (let i = 64; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)]; + hash = crypto.createHash("sha256").update(result).digest("base64"); + hash = hash.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); + + return [result, hash]; + } + getNonce() { + const timestamp = Date.now(); + let hash = crypto.createHash("sha256").update(timestamp.toString()).digest("base64"); + hash = hash.slice(0, hash.length - 1); + return hash; + } + toHexString(byteArray) { + return Array.prototype.map + .call(byteArray, function (byte) { + return ("0" + (byte & 0xff).toString(16).toUpperCase()).slice(-2); + }) + .join(""); + } + + toByteArray(hexString) { + const result = []; + for (let i = 0; i < hexString.length; i += 2) { + result.push(parseInt(hexString.substr(i, 2), 16)); + } + return result; + } + stringIsAValidUrl(s) { + try { + new URL(s); + return true; + } catch (err) { + this.log.debug(err); + return false; + } + } + randomString(length) { + let result = ""; + const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + const charactersLength = characters.length; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; + } + toCammelCase(string) { + return string.replace(/-([a-z])/g, function (g) { + return g[1].toUpperCase(); + }); + } + extractHidden(body) { + const returnObject = {}; + if (!body) return returnObject; + let matches; + if (body.matchAll) { + matches = body.matchAll(/]* name=["']([^'"]*)|)(?=[^>]* value=["']([^'"]*)|)/g); + } else { + this.log.warn( + "The adapter needs in the future NodeJS v12. https://forum.iobroker.net/topic/22867/how-to-node-js-f%C3%BCr-iobroker-richtig-updaten", + ); + matches = this.matchAll(/]* name=["']([^'"]*)|)(?=[^>]* value=["']([^'"]*)|)/g, body); + } + for (const match of matches) { + returnObject[match[1]] = match[2]; + } + return returnObject; + } + extractStateToken(body) { + if (!body) return null; + const stateMatch = body.match(/]*name=["']state["'][^>]*value=["']([^"']*)["']/i); + if (stateMatch && stateMatch[1]) { + return stateMatch[1]; + } + return null; + } + async followRedirectsManually(redirectUrl, depth, code_verifier, reject, resolve) { + // Follow redirects manually until we hit weconnect:// (like Python does) + const maxDepth = 10; + + if (depth >= maxDepth) { + this.log.error("Too many redirects (max " + maxDepth + ")"); + reject(); + return; + } + + // Check if we've reached weconnect:// URL + if (redirectUrl.startsWith("weconnect://")) { + this.log.debug("Reached weconnect:// URL: " + redirectUrl.substring(0, 100) + "..."); + const code = this.extractCodeFromUrl(redirectUrl); + if (code) { + this.log.debug("Successfully extracted authorization code (JWT token)"); + this.exchangeCodeForTokens(code, code_verifier, reject, resolve); + return; + } else { + this.log.error("Could not extract code from weconnect:// URL"); + reject(); + return; + } + } + + // Continue following redirects + this.log.debug("Redirect " + (depth + 1) + ": " + redirectUrl.substring(0, 100) + "..."); + + try { + // Get cookies from request jar + const cookies = this.jar.getCookies(redirectUrl); + const cookieHeader = cookies.map((c) => c.cookieString()).join("; "); + + const response = await axios({ + method: "get", + url: redirectUrl, + headers: { + "User-Agent": this.userAgent, + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.9", + ...(cookieHeader ? { Cookie: cookieHeader } : {}), + }, + maxRedirects: 0, // Don't follow redirects automatically + validateStatus: (status) => status >= 200 && status < 400, // Accept redirects as valid + }); + + // Store any new cookies back into the jar + if (response.headers["set-cookie"]) { + response.headers["set-cookie"].forEach((cookie) => { + this.jar.setCookie(cookie, redirectUrl); + }); + } + + // Check for redirect response (302, 301, etc.) + if (response.status >= 300 && response.status < 400) { + const nextUrl = response.headers.location; + + if (!nextUrl) { + this.log.error("Redirect response but no location header"); + reject(); + return; + } + + // Check if this is a weconnect:// URL + if (nextUrl.startsWith("weconnect://")) { + this.log.debug("Found weconnect:// redirect: " + nextUrl.substring(0, 100) + "..."); + const code = this.extractCodeFromUrl(nextUrl); + if (code) { + this.log.debug("Successfully extracted authorization code (JWT token)"); + this.exchangeCodeForTokens(code, code_verifier, reject, resolve); + return; + } + } + + // Check for error redirects + if (nextUrl.includes("/error")) { + this.log.error("Authentication error redirect: " + nextUrl); + if (nextUrl.includes("error=access_denied")) { + this.log.warn("========================================"); + this.log.warn("WICHTIG: Bitte loggen Sie sich einmal in der VW Connect App ein und akzeptieren Sie die neuen Nutzungsbedingungen."); + this.log.warn("IMPORTANT: Please log in to the VW Connect App once and accept the new terms and conditions."); + this.log.warn("========================================"); + } + reject(); + return; + } + + // Make relative URLs absolute + let absoluteUrl = nextUrl; + if (nextUrl.startsWith("/")) { + const urlObj = new URL(redirectUrl); + absoluteUrl = urlObj.protocol + "//" + urlObj.host + nextUrl; + } else if (!nextUrl.startsWith("http") && !nextUrl.startsWith("weconnect://")) { + absoluteUrl = redirectUrl + "/" + nextUrl; + } + + // Continue following + await this.followRedirectsManually(absoluteUrl, depth + 1, code_verifier, reject, resolve); + } else if (response.status === 200) { + // Check if we ended up at a consent/terms page + if (response.data && (response.data.includes("termsAndConditions") || response.data.includes("consent"))) { + this.log.warn("========================================"); + this.log.warn("WICHTIG: Bitte loggen Sie sich einmal in der VW Connect App ein und akzeptieren Sie die neuen Nutzungsbedingungen oder öffne die Final url im Browser."); + this.log.warn("IMPORTANT: Please log in to the VW Connect App once and accept the new terms and conditions. Or open the final url in the browser."); + this.log.warn("Final URL: " + redirectUrl); + this.log.warn("========================================"); + reject(); + return; + } + + this.log.error("Got 200 OK but expected redirect or weconnect:// URL"); + this.log.debug("Response body preview: " + (response.data ? response.data.substring(0, 200) : "empty")); + reject(); + } + } catch (error) { + this.log.error("Error during redirect following: " + error.message); + reject(); + } + } + extractCodeFromUrl(url) { + // Extract authorization code from weconnect:// URL + // Can be in query string: weconnect://authenticated?code=eyJ...&state=abc123 + // Or in fragment: weconnect://authenticated#code=eyJ...&state=abc123 + try { + this.log.debug("Extracting code from URL: " + url.substring(0, 150) + "..."); + + const urlObj = new URL(url); + + // Try query string first + let code = urlObj.searchParams.get("code"); + if (code) { + this.log.debug("Extracted code from query string: " + code.substring(0, 50) + "..."); + return code; + } + + // Try fragment (hash) - parse it like a query string + if (urlObj.hash) { + const fragment = urlObj.hash.substring(1); // Remove # + const fragmentParams = new URLSearchParams(fragment); + code = fragmentParams.get("code"); + if (code) { + this.log.debug("Extracted code from fragment: " + code.substring(0, 50) + "..."); + return code; + } + + // Also check for access_token in fragment (alternate OAuth flow) + const accessToken = fragmentParams.get("access_token"); + if (accessToken) { + this.log.debug("Found access_token in fragment instead of code"); + this.log.debug("This appears to be implicit flow, not authorization code flow"); + // Return null as we need 'code', not 'access_token' + } + } + + this.log.debug("No code parameter found in URL"); + } catch (error) { + this.log.debug("Error parsing URL: " + error.message); + } + return null; + } + async exchangeCodeForTokens(code, code_verifier, reject, resolve) { + this.log.debug("Exchanging authorization code for tokens (Python-style, no code_verifier)"); + + // CRITICAL: Use BFF token endpoint, NOT identity.vwgroup.io! + // Python gets this from: https://emea.bff.cariad.digital/login/v1/idk/openid-configuration + const tokenEndpoint = "https://emea.bff.cariad.digital/login/v1/idk/token"; + + // Exchange code for tokens WITHOUT code_verifier (like Python does) + const tokenBody = { + client_id: this.clientId, + grant_type: "authorization_code", + code: code, + redirect_uri: this.redirect, + // NO code_verifier - Python doesn't use it! + }; + + try { + // Get cookies from jar for this request + const cookies = this.jar.getCookies(tokenEndpoint); + const cookieHeader = cookies.map((c) => c.cookieString()).join("; "); + + const response = await axios({ + method: "post", + url: tokenEndpoint, + headers: { + "Content-Type": "application/x-www-form-urlencoded", + "User-Agent": this.userAgent, + "Accept": "application/json", + "Accept-Language": "en-US,en;q=0.9", + ...(cookieHeader ? { Cookie: cookieHeader } : {}), + }, + data: new URLSearchParams(tokenBody).toString(), + maxRedirects: 0, + }); + + // Store any new cookies back into the jar + if (response.headers["set-cookie"]) { + response.headers["set-cookie"].forEach((cookie) => { + this.jar.setCookie(cookie, tokenEndpoint); + }); + } + + this.log.debug("Token exchange successful"); + const tokens = response.data; + + // Store tokens directly like Python does + this.config.atoken = tokens.access_token; + this.config.rtoken = tokens.refresh_token; + + this.log.debug("Tokens received and stored, continuing with VW-specific flow"); + + // Continue with VW-specific token handling + await this.getVWToken(tokens, tokens.id_token, reject, resolve); + } catch (error) { + this.log.error("Failed to exchange code for tokens at OpenID endpoint"); + if (error.response) { + this.log.error("Status: " + error.response.status); + this.log.error("Response: " + JSON.stringify(error.response.data)); + } else { + this.log.error(error.message); + } + reject(); + } + } + matchAll(re, str) { + let match; + const matches = []; + + while ((match = re.exec(str))) { + // add all matched groups + matches.push(match); + } + + return matches; + } + /** + * Is called when adapter shuts down - callback has to be called under any circumstances! + * @param {() => void} callback + */ + onUnload(callback) { + try { + this.setState("info.connection", false, true); + this.log.info("cleaned everything up..."); + clearInterval(this.refreshTokenInterval); + clearInterval(this.vwrefreshTokenInterval); + clearInterval(this.updateInterval); + clearInterval(this.fupdateInterval); + clearTimeout(this.refreshTokenTimeout); + clearTimeout(this.refreshTimeout); + this.mqttClient && this.mqttClient.end(); + callback(); + } catch (e) { + this.log.error(e); + callback(); + } + } + + /** + * Is called if a subscribed state changes + * @param {string} id + * @param {ioBroker.State | null | undefined} state + */ + async onStateChange(id, state) { + try { + if (state) { + if (!state.ack) { + const vin = id.split(".")[2]; + let body = ""; + let contentType = ""; + if (vin === "refresh") { + this.updateStatus(); + return; + } + if (id.indexOf("remote.refresh") !== -1) { + this.updateStatus(); + return; + } + if (id.indexOf("remote.forceRefresh") !== -1) { + this.requestStatusUpdate(vin); + return; + } + if (id.indexOf("startCharging") !== -1) { + const idArray = id.split("."); + idArray.pop(); + idArray.push("id"); + const stationId = (await this.getStateAsync(idArray.join("."))).val; + // this.log.info("Start charging for id: " + stationID); + axios({ + method: "post", + maxBodyLength: Infinity, + url: "https://prod.emea.mobile.charging.cariad.digital/charging_stations/start", + headers: { + "X-Api-Version": "2", + traceparent: "00-91bf3a6b198647bda6368a0be4626811-xxxxxxxx-01", + Authorization: "Bearer " + this.config.atoken, + Connection: "Keep-Alive", + "User-Agent": "okhttp/4.12.0", + "X-Cookie": "4olkEx88/5FdWgb95anxwEwm6Rwp1YOxD6NKoBM4nw5j0ZmxxxxxxP+QLA==", + "X-Debug-Log": "true", + "Content-Type": "application/json", + "Accept-Language": "de-DE", + "X-Brand": this.xbrand, + "X-Platform": "android", + "X-Device-Timezone": "Europe/Berlin", + "X-Sdk-Version": "4.5.4-(2025.18.0)", + "X-Use-BffError-V2": "true", + "X-Device-Manufacturer": "google", + "X-Device-Name": "Pixel 4a", + "X-Device-OS-Name": "13", + "X-Device-OS-Version": "33", + }, + data: { stationId }, + }) + .then((response) => { + this.log.info(JSON.stringify(response.data)); + }) + .catch((error) => { + this.log.error("Failed to start Charging"); + this.log.error(error); + error.response && this.log.error(error.response.status.toString()); + error.response && this.log.error(JSON.stringify(error.response.data)); + }); + } + if (id.indexOf("stopCharging") !== -1) { + const idArray = id.split("."); + idArray.pop(); + idArray.push("id"); + const sessionId = (await this.getStateAsync(idArray.join("."))).val; + axios({ + method: "post", + maxBodyLength: Infinity, + url: "https://prod.emea.mobile.charging.cariad.digital/charging_stations/stop", + headers: { + "X-Api-Version": "2", + traceparent: "00-91bf3a6b198647bda6368a0be4626811-xxxxxxxx-01", + Authorization: "Bearer " + this.config.atoken, + Connection: "Keep-Alive", + "User-Agent": "okhttp/4.12.0", + "X-Cookie": "4olkEx88/5FdWgb95anxwEwm6Rwp1YOxD6NKoBM4nw5j0ZmxxxxxxP+QLA==", + "X-Debug-Log": "true", + "Content-Type": "application/json", + "Accept-Language": "de-DE", + "X-Brand": this.xbrand, + "X-Platform": "android", + "X-Device-Timezone": "Europe/Berlin", + "X-Sdk-Version": "4.5.4-(2025.18.0)", + "X-Use-BffError-V2": "true", + "X-Device-Manufacturer": "google", + "X-Device-Name": "Pixel 4a", + "X-Device-OS-Name": "13", + "X-Device-OS-Version": "33", + }, + data: { sessionId }, + }) + .then((response) => { + this.log.info(JSON.stringify(response.data)); + }) + .catch((error) => { + this.log.error("Failed to stop Charging"); + this.log.error(error); + error.response && this.log.error(error.response.status.toString()); + error.response && this.log.error(JSON.stringify(error.response.data)); + }); + } + if (id.indexOf("Settings.") != -1) { + if (this.config.type === "id" || this.config.type === "audietron") { + const action = id.split(".")[5]; + const settingsPath = id.split(".")[4]; + const pre = this.name + "." + this.instance; + const states = await this.getStatesAsync(pre + "." + vin + ".status." + settingsPath + ".*"); + const body = {}; + const allIds = Object.keys(states); + allIds.forEach((keyName) => { + const key = keyName.split(".").splice(-1)[0]; + if (key.indexOf("Timestamp") === -1) { + body[key] = states[keyName].val; + } + }); + body[action] = state.val; + if (action === "targetTemperature_C") { + body["targetTemperature"] = state.val; + body["targetTemperatureUnit"] = "celsius"; + } + const firstPart = settingsPath.split("Settings")[0]; + this.setIdRemote(vin, firstPart, "settings", body).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } + } + if (id.indexOf(".settings.") != -1) { + if (this.config.type === "skodae") { + const idArray = id.split("."); + const action = idArray[idArray.length - 1]; + const settingsPath = id.split(".")[4]; + const pre = this.name + "." + this.instance; + const states = await this.getStatesAsync(pre + "." + vin + ".status." + settingsPath + ".settings.*"); + const body = {}; + const allIds = Object.keys(states); + allIds.forEach((keyName) => { + const keyNameArray = keyName.split("."); + const key = keyNameArray[keyNameArray.length - 1]; + const subKey = keyNameArray[keyNameArray.length - 2]; + if (subKey === "settings" && states[keyName]) { + body[key] = states[keyName].val; + if (key === action) { + body[action] = state.val; + } + } else if (states[keyName]) { + if (!body[subKey]) { + body[subKey] = {}; + } + body[subKey][key] = states[keyName].val; + if (key === action) { + body[subKey][action] = state.val; + } + } + }); + this.setSkodaESettings(vin, settingsPath, "UpdateSettings", body).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } + } + + if (id.indexOf("remote.") !== -1) { + const action = id.split(".")[4]; + if (action === "batterycharge") { + body = '\n\n start\n'; + if (state.val === false) { + body = '\n\n stop\n'; + } + contentType = "application/vnd.vwg.mbb.ChargerAction_v1_0_0+xml"; + this.setVehicleStatus( + vin, + "$homeregion/fs-car/bs/batterycharge/v1/$type/$country/vehicles/$vin/charger/actions", + body, + contentType, + ).catch(() => { + this.log.error("failed set state"); + }); + } + if (action === "chargeMinLimit") { + body = ` + + setChargeMinLimit + + + + + + + ${state.val} + + + `; + + contentType = "application/vnd.vwg.mbb.timeraction_v1_0_0+xml"; + this.setVehicleStatus( + vin, + "$homeregion/fs-car/bs/departuretimer/v1/$type/$country/vehicles/$vin/timer/actions", + body, + contentType, + ).catch(() => { + this.log.error("failed set min limit state"); + }); + } + if (action === "maxChargeCurrent") { + body = + '\nsetSettings \n \n' + + state.val + + " \n \n"; + contentType = "application/vnd.vwg.mbb.ChargerAction_v1_0_0+xml"; + this.setVehicleStatus( + vin, + "$homeregion/fs-car/bs/batterycharge/v1/$type/$country/vehicles/$vin/charger/actions", + body, + contentType, + ).catch(() => { + this.log.error("failed set state"); + }); + } + if (action === "charging") { + if (this.config.type === "id" || this.config.type === "audietron") { + const value = state.val ? "start" : "stop"; + this.setIdRemote(vin, action, value).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } else if (this.config.type === "seatcupra") { + const value = state.val ? "start" : "stop"; + this.setSeatCupraStatus(vin, action, value).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } else if (this.config.type === "skodae") { + const value = state.val ? "start" : "stop"; + this.setSkodaESettings(vin, action, value).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } + } + if (action === "auxiliaryheating") { + if (this.config.type === "id" || this.config.type === "audietron") { + const value = state.val ? "start" : "stop"; + this.setIdRemote(vin, action, value).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } else if (this.config.type === "seatcupra") { + const value = state.val ? "start" : "stop"; + this.setSeatCupraStatus(vin, action, value).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } else if (this.config.type === "skodae") { + this.setSkodaESettings(vin, action, state.val).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } + } + if (action === "windowheating") { + if (this.config.type === "id" || this.config.type === "audietron") { + const value = state.val ? "start" : "stop"; + this.setIdRemote(vin, action, value).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } else if (this.config.type === "seatcupra") { + const value = state.val ? "start" : "stop"; + this.setSeatCupraStatus(vin, action, value).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } else if (this.config.type === "skodae") { + const value = state.val ? "start" : "stop"; + this.setSkodaESettings(vin, action, value).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } + } + if (action === "access") { + if (this.config.type === "id" || this.config.type === "audietron") { + const value = state.val ? "lock" : "unlock"; + this.setIdRemote(vin, action, value).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } else if (this.config.type === "seatcupra") { + const value = state.val ? "lock" : "unlock"; + this.setSeatCupraStatus(vin, action, value).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } else if (this.config.type === "skodae") { + const value = state.val ? "lock" : "unlock"; + this.setSkodaESettings(vin, action, value).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } + } + if (action === "air-conditioning") { + if (this.config.type === "skodae") { + const value = state.val ? "start" : "stop"; + this.setSkodaESettings(vin, action, value).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } + } + if (action === "ventilation") { + if (this.config.type === "skodae") { + const value = state.val ? "start" : "stop"; + this.setSkodaESettings(vin, action, value).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } + } + if (action === "targetTemperatureInCelsius") { + if (this.config.type === "skodae") { + this.setSkodaESettings(vin, action, state.val).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } + if (this.config.type === "seatcupra") { + this.setSeatCupraStatus(vin, action, state.val).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } + } + if ( + action === "climatisation" || + action === "climatisationv2" || + action === "climatisationv3" || + action === "standheizungv3" + ) { + let secToken = null; + if ((this.config.type === "id" || this.config.type === "audietron") && action !== "climatisationv3") { + const value = state.val ? "start" : "stop"; + this.setIdRemote(vin, action, value).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } else if (this.config.type === "seatcupra") { + const value = state.val ? "start" : "stop"; + this.setSeatCupraStatus(vin, action, value).catch((error) => { + this.log.error("failed set state " + action); + this.log.error(error); + }); + return; + } else { + body = + '\n\n startClimatisation\n'; + if (action === "climatisationv2" || action === "standheizungv3") { + const heaterSourceState = await this.getStateAsync(vin + ".climater.settings.heaterSource.content"); + let heaterSource = "electric"; + if (action === "standheizungv3") { + heaterSource = "automatic"; + + secToken = await this.requestSecToken(vin, "rclima_v1/operations/P_START_CLIMA_AU"); + } + if (heaterSourceState.val) { + heaterSource = heaterSourceState.val; + } + const tagetTempState = await this.getStateAsync(vin + ".climater.settings.targetTemperature.content"); + let targetTemp = 2950; + if (tagetTempState && tagetTempState.val) { + targetTemp = tagetTempState.val; + } + body = + '\n\n startClimatisation ' + + targetTemp + + " true " + + heaterSource + + " \n"; + } + if (state.val === false) { + body = + '\n\n stopClimatisation\n'; + } + contentType = "application/vnd.vwg.mbb.ClimaterAction_v1_0_2+xml"; + if (action === "climatisationv3") { + const heaterSourceState = await this.getStateAsync(vin + ".climater.settings.heaterSource.content"); + let heaterSource = "electric"; + if (heaterSourceState && heaterSourceState.val) { + heaterSource = heaterSourceState.val; + } + const isMirrorHeatingEnabledState = await this.getStateAsync( + vin + ".climater.settings.climaterElementSettings.isMirrorHeatingEnabled.content", + ); + let isMirror = true; + if (isMirrorHeatingEnabledState && isMirrorHeatingEnabledState.val) { + isMirror = isMirrorHeatingEnabledState.val; + } + const tagetTempState = await this.getStateAsync(vin + ".climater.settings.targetTemperature.content"); + let targetTemp = 2950; + if (tagetTempState && tagetTempState.val) { + targetTemp = tagetTempState.val; + } + + body = { + action: { + type: "startClimatisation", + settings: { + targetTemperature: targetTemp, + heaterSource: heaterSource, + climaterElementSettings: { + isMirrorHeatingEnabled: isMirror, + zoneSettings: { + zoneSetting: [ + { + value: { + position: "frontLeft", + isEnabled: true, + }, + }, + { + value: { + position: "frontRight", + isEnabled: true, + }, + }, + { + value: { + position: "rearLeft", + isEnabled: true, + }, + }, + { + value: { + position: "rearRight", + isEnabled: true, + }, + }, + ], + }, + }, + }, + }, + }; + if (state.val === false) { + body = { + action: { + type: "stopClimatisation", + }, + }; + } + contentType = "application/json; charset=utf-8"; + body = JSON.stringify(body); + this.log.debug(body); + } + this.setVehicleStatus( + vin, + "$homeregion/fs-car/bs/climatisation/v1/$type/$country/vehicles/$vin/climater/actions", + body, + contentType, + secToken, + ).catch(() => { + this.log.error("failed set state"); + }); + } + } + + if (action === "ventilation" || action === "ventilationv2" || action === "ventilationv3") { + const idArray = id.split("."); + idArray.pop(); + idArray.push(action + "Duration"); + const ventilationDurationPath = idArray.join("."); + const durationState = await this.getStateAsync(ventilationDurationPath); + let duration = 30; + if (durationState && durationState.val) { + duration = durationState.val; + } + let body = + '\n\n \n true\n' + + duration + + "\n " + + action + + "\n"; + if (state.val === false) { + body = + '\n\n \n false\n \n'; + } + contentType = "application/vnd.vwg.mbb.RemoteStandheizung_v2_0_2+xml"; + if (action === "ventilationv2") { + body = + '{"performAction":{"quickstart":{"startMode":"ventilation","active":true,"climatisationDuration":' + + duration + + "}}}"; + if (state.val === false) { + body = '{"performAction":{"quickstop":{"active":false}}}'; + } + contentType = "application/vnd.vwg.mbb.RemoteStandheizung_v2_0_2+json"; + } + + let secToken = await this.requestSecToken(vin, "rheating_v1/operations/P_QSACT"); + if (action === "ventilationv3") { + body = '{"performAction":{"quickstart":{"active":true,"climatisationDuration":' + duration + "}}}"; + if (state.val === false) { + body = '{"performAction":{"quickstop":{"active":false}}}'; + secToken = null; + } + contentType = "application/json; charset=utf-8"; + } + this.setVehicleStatus( + vin, + "$homeregion/fs-car/bs/rs/v1/$type/$country/vehicles/$vin/action", + body, + contentType, + secToken, + ).catch(() => { + this.log.error("failed set state"); + }); + } + + if (action === "climatisationTemperature") { + let temp = 2950; + if (state.val && !isNaN(state.val)) { + temp = (parseFloat(state.val) + 273) * 10; + } + + const climatisationWithoutHVpowerState = await this.getStateAsync( + vin + ".climater.settings.climatisationWithoutHVpower.content", + ); + let climatisationWithoutHVpower = false; + + if (climatisationWithoutHVpowerState.val) { + climatisationWithoutHVpower = climatisationWithoutHVpowerState.val; + } + const heaterSourceState = await this.getStateAsync(vin + ".climater.settings.heaterSource.content"); + let heaterSource = "electric"; + if (heaterSourceState.val) { + heaterSource = heaterSourceState.val; + } + body = + '\n\n setSettings ' + + temp + + " " + + climatisationWithoutHVpower + + " " + + heaterSource + + " \n"; + contentType = "application/vnd.vwg.mbb.ClimaterAction_v1_0_0+xml"; + this.setVehicleStatus( + vin, + "$homeregion/fs-car/bs/climatisation/v1/$type/$country/vehicles/$vin/climater/actions", + body, + contentType, + ).catch(() => { + this.log.error("failed set state"); + }); + } + + if (action === "windowheating") { + body = + '\n\n startWindowHeating\n'; + if (state.val === false) { + body = + '\n\n stopWindowHeating\n'; + } + contentType = "application/vnd.vwg.mbb.ClimaterAction_v1_0_0+xml"; + this.setVehicleStatus( + vin, + "$homeregion/fs-car/bs/climatisation/v1/$type/$country/vehicles/$vin/climater/actions", + body, + contentType, + ).catch(() => { + this.log.error("failed set state"); + }); + } + + if (action === "honkandflash" || action === "flash") { + if (this.config.type === "id" || this.config.type === "audietron") { + this.setIdRemote(vin, action).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } + } + if (action === "vehiclewakeuptrigger") { + if (this.config.type === "id" || this.config.type === "audietron") { + this.setIdRemote(vin, action).catch(() => { + this.log.error("failed set state " + action); + }); + return; + } + } + if (action === "flash") { + //HONK_AND_FLASH + const idArray = id.split("."); + idArray.pop(); + idArray.pop(); + idArray.push("position.carCoordinate"); + const posId = idArray.join("."); + const longitude = await this.getStateAsync(posId + ".longitude"); + const latitude = await this.getStateAsync(posId + ".latitude"); + if (!longitude || !latitude) { + this.log.info("No Location available, location information needed for this action"); + return; + } + body = + '{"honkAndFlashRequest":{"serviceOperationCode":"FLASH_ONLY","userPosition":{"latitude":' + + latitude.val + + ',"longitude":' + + longitude.val + + "}}}"; + contentType = "application/json; charset=UTF-8"; + this.setVehicleStatus( + vin, + "$homeregion/fs-car/bs/rhf/v1/$type/$country/vehicles/$vin/honkAndFlash", + body, + contentType, + ).catch(() => { + this.log.error("failed set state"); + }); + } + + if (action === "honk") { + /* + { + "duration_s": 15, + "mode": "flash", + "userPosition": { + "latitude": 48.2312143, + "longitude": 11.232123 + } +}*/ + const idArray = id.split("."); + idArray.pop(); + idArray.pop(); + idArray.push("position.carCoordinate"); + const posId = idArray.join("."); + const longitude = await this.getStateAsync(posId + ".longitude"); + const latitude = await this.getStateAsync(posId + ".latitude"); + if (!longitude || !latitude) { + this.log.info("No Location available, location information needed for this action"); + return; + } + body = + '{"honkAndFlashRequest":{"serviceOperationCode":"HONK_AND_FLASH","userPosition":{"latitude":' + + latitude.val + + ',"longitude":' + + longitude.val + + "}}}"; + contentType = "application/json; charset=UTF-8"; + this.setVehicleStatus( + vin, + "$homeregion/fs-car/bs/rhf/v1/$type/$country/vehicles/$vin/honkAndFlash", + body, + contentType, + ).catch(() => { + this.log.error("failed set state"); + }); + } + + if (action === "standheizung" || action === "standheizungv2") { + body = + '\n\n \n true\n \n'; + if (state.val === false) { + body = + '\n\n \n false\n \n'; + } + contentType = "application/vnd.vwg.mbb.RemoteStandheizung_v2_0_0+xml"; + if (action === "standheizungv2") { + body = + '{"performAction":{"quickstart":{"startMode":"heating","active":true,"climatisationDuration":30}}}'; + if (state.val === false) { + body = '{"performAction":{"quickstop":{"active":false}}}'; + } + contentType = "application/vnd.vwg.mbb.RemoteStandheizung_v2_0_2+json"; + } + + const secToken = await this.requestSecToken(vin, "rheating_v1/operations/P_QSACT"); + this.setVehicleStatus( + vin, + "$homeregion/fs-car/bs/rs/v1/$type/$country/vehicles/$vin/action", + body, + contentType, + secToken, + ).catch(() => { + this.log.error("failed set state"); + }); + } + if (action === "lock" || action === "lockv2") { + body = + '\n\n lock\n'; + let lockAction = "LOCK"; + if (state.val === false) { + body = + '\n\n unlock\n'; + lockAction = "UNLOCK"; + } + contentType = "application/vnd.vwg.mbb.RemoteLockUnlock_v1_0_0+xml"; + const secToken = await this.requestSecToken(vin, "rlu_v1/operations/" + lockAction); + this.setVehicleStatus( + vin, + "$homeregion/fs-car/bs/rlu/v1/$type/$country/vehicles/$vin/actions", + body, + contentType, + secToken, + ).catch(() => { + this.log.error("failed set state"); + }); + } + } + + this.refreshTimeout && clearTimeout(this.refreshTimeout); + this.refreshTimeout = setTimeout(async () => { + this.updateStatus(); + }, 10 * 1000); + } else { + const vin = id.split(".")[2]; + if (id.indexOf("climatisationState.content") !== -1) { + let value = false; + if (state.val === "on" || state.val === "heating") { + value = true; + } + this.setState(vin + ".remote.climatisation", value, true); + this.setState(vin + ".remote.climatisationv2", value, true); + } + if (id.indexOf("maxChargeCurrent.content") !== -1) { + this.setState(vin + ".remote.maxChargeCurrent", state.val, true); + } + if (id.indexOf(".statusv2.locked") !== -1) { + this.setState(vin + ".remote.access", state.val, true); + } + if (id.indexOf("climatisationStatus.climatisationState") !== -1) { + let value = false; + if (state.val === "on" || state.val === "heating") { + value = true; + } + this.setState(vin + ".remote.climatisation", value, true); + } + if (id.indexOf("chargingStatus.chargingState") !== -1) { + if (this.config.type === "id" || this.config.type === "audietron") { + this.setState(vin + ".remote.charging", state.val !== "readyForCharging" ? true : false, true); + } else { + this.setState(vin + ".remote.batterycharge", state.val !== "readyForCharging" ? true : false, true); + } + } + if (id.indexOf("status.charging.state") !== -1) { + if (this.config.type === "skodae") { + this.setState(vin + ".remote.charging", state.val === "On" ? true : false, true); + } + } + if (id.indexOf("air-conditioning.status.state") !== -1) { + if (this.config.type === "skodae") { + this.setState(vin + ".remote.air-conditioning", state.val === "On" ? true : false, true); + } + } + if (id.indexOf("ventilation.status.state") !== -1) { + if (this.config.type === "skodae") { + this.setState(vin + ".remote.ventilation", state.val === "On" ? true : false, true); + } + } + if (id.indexOf("settings.targetTemperatureInKelvin") !== -1) { + if (this.config.type === "skodae") { + this.setState(vin + ".remote.targetTemperatureInCelsius", state.val - 273.15, true); + } + } + if (id.indexOf(".status.isCarLocked") !== -1) { + if (this.hasRemoteLock === true) { + this.setState(vin + ".remote.lock", state.val, true); + } + } + if (id.endsWith(".carCoordinate.latitude")) { + await this.setLatitude(vin, state.val / 1000000); + } + if (id.endsWith(".carCoordinate.longitude")) { + await this.setLongitude(vin, state.val / 1000000); + } + if (id.endsWith(".position.latitude")) { + await this.setLatitude(vin, parseFloat(state.val)); + } + if (id.endsWith(".position.longitude")) { + await this.setLongitude(vin, parseFloat(state.val)); + } + // Gather general values from ID. models + if (id.endsWith("accessStatus.doorLockStatus")) { + this.setIsCarLocked(vin, state.val === "locked"); + } + if (id.endsWith(".parkingposition.lat")) { + this.setLatitude(vin, state.val); + } + if (id.endsWith(".parkingposition.lon")) { + this.setLongitude(vin, state.val); + } + if (id.indexOf(".windowHeatingStatus") !== -1) { + if (state.val && state.val.toLowerCase) { + this.setState(vin + ".remote.windowheating", state.val.toLowerCase() === "off" ? false : true, true); + } + } + } + } else { + // The state was deleted + // this.log.info(`state ${id} deleted`); + } + } catch (err) { + this.log.error("Error in OnStateChange: " + err); + this.log.error(err.stack); + } + } + + async setPositionChanel(vin) { + await this.extendObjectAsync(vin + ".position", { + type: "channel", + common: { + name: "Position", + }, + native: {}, + }); + } + + async setLatitude(vin, value) { + await this.setPositionChanel(vin); + await this.extendObjectAsync(vin + ".position.latitudeConv", { + type: "state", + common: { + name: "latitude converted", + role: "indicator", + type: "mixed", + write: false, + read: true, + }, + native: {}, + }); + await this.setStateAsync(vin + ".position.latitudeConv", value, true); + await this.updateGeohash(vin); + } + + async setLongitude(vin, value) { + await this.setPositionChanel(); + await this.extendObjectAsync(vin + ".position.longitudeConv", { + type: "state", + common: { + name: "longitude converted", + role: "indicator", + type: "mixed", + write: false, + read: true, + }, + native: {}, + }); + await this.setStateAsync(vin + ".position.longitudeConv", value, true); + await this.updateGeohash(vin); + } + + async updateGeohash(vin) { + await this.sleep(5000); //wait for all states + const latitude = await this.getStateAsync(vin + ".position.latitudeConv"); + if (latitude == null) { + return; + } + const longitude = await this.getStateAsync(vin + ".position.longitudeConv"); + if (longitude == null) { + return; + } + if (this.isFirstLocation === true) { + this.isFirstLocation = false; + } else { + // Update only if one of both have been changed + if (latitude.ts !== latitude.lc && longitude.ts !== longitude.lc) { + this.log.debug( + "No update lat ts " + + latitude.ts + + " <-> lc " + + latitude.lc + + ", long ts " + + longitude.ts + + " <-> lc " + + longitude.lc, + ); + return; + } + // Update only if both longitude and latitude were updated within the same 3 seconds. + // Otherwise only one value of both were updated yet and coordinates are not yet valid. + if (Math.abs(latitude.lc - longitude.lc) > 3000) { + this.log.debug("No update lat = " + latitude.lc + ", long =" + longitude.lc); + return; + } + } + + const latitudeValue = latitude.val; + const longitudeValue = longitude.val; + await this.extendObjectAsync(vin + ".position.geohash", { + type: "state", + common: { + name: "Geohash", + role: "indicator", + type: "mixed", + write: false, + read: true, + }, + native: {}, + }); + await this.setStateAsync(vin + ".position.geohash", geohash.encode(latitudeValue, longitudeValue), true); + if (!this.config.reversePos) { + this.log.debug("reverse pos deactivated"); + return; + } + await this.reversePosition(latitudeValue, longitudeValue, vin); + } + sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + async reversePosition(latitudeValue, longitudeValue, vin) { + this.log.debug("reverse pos started"); + + request.get( + { + url: + "https://nominatim.openstreetmap.org/reverse?lat=" + + latitudeValue + + "&lon=" + + longitudeValue + + "&format=json", + + headers: { + "User-Agent": "ioBroker/vw-connect", + }, + json: true, + followAllRedirects: true, + }, + async (err, resp, body) => { + this.log.debug("reverse pos received"); + this.log.debug(JSON.stringify(body)); + if (err || resp.statusCode >= 400 || !body) { + body && this.log.error(JSON.stringify(body)); + resp && this.log.error(resp.statusCode.toString()); + err && this.log.error(err); + return; + } + if (body.display_name) { + try { + const timestamp = Date.now(); + const number = body.address.house_number || ""; + const city = body.address.city || body.address.town || body.address.village; + const fullAdress = + body.address.road + + (number == "" ? "" : " ") + // skip blank if house number missing + number + + ", " + + body.address.postcode + + " " + + city + + ", " + + body.address.country; + await this.extendObjectAsync(vin + ".position.address.displayName", { + type: "state", + common: { + name: "displayName", + role: "indicator", + type: "mixed", + write: false, + read: true, + }, + native: {}, + }); + await this.setStateAsync(vin + ".position.address.displayName", fullAdress, true); + const keys = Object.keys(body.address); + for (const keyIndex in keys) { + const key = keys[keyIndex]; + await this.extendObjectAsync(vin + ".position.address." + key, { + type: "state", + common: { + name: key, + role: "indicator", + type: "mixed", + write: false, + read: true, + }, + native: {}, + }); + await this.setStateAsync(vin + ".position.address." + key, body.address[key], true); + } + this.setOtherStatesInChannelNull(vin + ".position.address", timestamp); + } catch (err) { + this.log.error(err); + } + } else { + this.log.error(JSON.stringify(body)); + } + }, + ); + } } // @ts-ignore parent is a valid property on module if (module.parent) { - // Export the constructor in compact mode - /** - * @param {Partial} [options={}] - */ - module.exports = (options) => new VwWeconnect(options); + // Export the constructor in compact mode + /** + * @param {Partial} [options={}] + */ + module.exports = (options) => new VwWeconnect(options); } else { - // otherwise start the instance directly - new VwWeconnect(); -} \ No newline at end of file + // otherwise start the instance directly + new VwWeconnect(); +} diff --git a/main.test.js b/main.test.js index 307b4c62..e46d0f56 100644 --- a/main.test.js +++ b/main.test.js @@ -13,18 +13,17 @@ const { expect } = require("chai"); // import { functionToTest } from "./moduleToTest"; describe("module to test => function to test", () => { - // initializing logic - const expected = 5; - - it(`should return ${expected}`, () => { - const result = 5; - // assign result a value from functionToTest - expect(result).to.equal(expected); - // or using the should() syntax - result.should.equal(expected); - }); - // ... more tests => it + // initializing logic + const expected = 5; + it(`should return ${expected}`, () => { + const result = 5; + // assign result a value from functionToTest + expect(result).to.equal(expected); + // or using the should() syntax + result.should.equal(expected); + }); + // ... more tests => it }); // ... more test suites => describe diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..03972bf0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5372 @@ +{ + "name": "iobroker.vw-connect", + "version": "0.7.15", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "iobroker.vw-connect", + "version": "0.7.15", + "license": "MIT", + "dependencies": { + "@iobroker/adapter-core": "^3.3.2", + "@peculiar/webcrypto": "^1.5.0", + "axios": "^1.13.2", + "json-bigint": "^1.0.0", + "json2iob": "^2.6.21", + "mqtt": "^5.14.1", + "ngeohash": "^0.6.3", + "qs": "^6.14.0", + "request": "^2.88.2", + "traverse": "^0.6.11", + "uuid": "^11.1.0" + }, + "devDependencies": { + "@alcalzone/release-script": "^3.8.0", + "@alcalzone/release-script-plugin-iobroker": "^3.7.2", + "@alcalzone/release-script-plugin-license": "^3.7.0", + "@alcalzone/release-script-plugin-manual-review": "^3.7.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "^9.39.1", + "@iobroker/testing": "^5.2.2", + "@types/node": "^22.19.1", + "eslint": "^9.39.1", + "eslint-plugin-promise": "^7.2.1", + "globals": "^16.5.0", + "typescript": "~5.9.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@alcalzone/esbuild-register": { + "version": "2.5.1-1", + "resolved": "https://registry.npmjs.org/@alcalzone/esbuild-register/-/esbuild-register-2.5.1-1.tgz", + "integrity": "sha512-u19/K/ZaiWQhYIQZeF2FImUeJDiMD4i2h7qPdU0HVFCxoQynye59N4oy6EvjxeXaxLnGkppcb5KKeiYD4cT1iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.11.5", + "jsonc-parser": "^3.0.0" + } + }, + "node_modules/@alcalzone/pak": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@alcalzone/pak/-/pak-0.10.2.tgz", + "integrity": "sha512-v+kM7HlfIVNLDlGBcbZvrG3yVK3rPLH5kIoGRJbCcoHwpUqQbfEMzXAy1ZrfP+zbI5phHw2PhgrXZr3z6nh7Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "axios": "^1.6.2", + "execa": "~5.0.1", + "fs-extra": "^10.1.0", + "semver": "^7.3.7", + "tiny-glob": "^0.2.9" + } + }, + "node_modules/@alcalzone/pak/node_modules/execa": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.1.tgz", + "integrity": "sha512-4hFTjFbFzQa3aCLobpbPJR/U+VoL1wdV5ozOWjeet0AWDeYr9UFGM1eUFWHX+VtOWFq4p0xXUXfW1YxUaP4fpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@alcalzone/release-script": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script/-/release-script-3.8.0.tgz", + "integrity": "sha512-9H3imn8o9n3Ekpkie9TMgPeJfgT6WmklCMtOZ1wuwvMw/obNA0Ao1ZZ8CJfHk1YNjiLGmWn0kQFEGTqz4DoD7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/release-script-core": "3.7.0", + "@alcalzone/release-script-plugin-changelog": "3.7.0", + "@alcalzone/release-script-plugin-exec": "3.7.0", + "@alcalzone/release-script-plugin-git": "3.8.0", + "@alcalzone/release-script-plugin-package": "3.7.3", + "@alcalzone/release-script-plugin-version": "3.7.0", + "alcalzone-shared": "^4.0.1", + "axios": "^1.6.2", + "enquirer": "^2.3.6", + "fs-extra": "^10.1.0", + "picocolors": "1.0.0", + "semver": "^7.5.2", + "source-map-support": "^0.5.21", + "yargs": "^17.4.1" + }, + "bin": { + "release-script": "bin/release.js" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@alcalzone/release-script-core": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-core/-/release-script-core-3.7.0.tgz", + "integrity": "sha512-4np4dBziwX/aNRhS/gpK8bwa0wpLe7oomzJ7YTUXf5bUtV/UTpN2a9tm5Bp7ElnisKj6N3AqHl4lVXRo4L9hYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@alcalzone/release-script-plugin-changelog": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-changelog/-/release-script-plugin-changelog-3.7.0.tgz", + "integrity": "sha512-AlLOIjIPP42uBmvcdYkfijYDzolyY6JmfbTmdxQDBLyrgYXnuUr2GaKxbpeWSbvcAuUhNvHCAyI6LI90X3OTEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/release-script-core": "3.7.0", + "alcalzone-shared": "^4.0.1", + "fs-extra": "^10.1.0" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@alcalzone/release-script-plugin-exec": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-exec/-/release-script-plugin-exec-3.7.0.tgz", + "integrity": "sha512-ZhlKGhxa71mLyYB1/ojzik2RKcSAeIjuwKzlWRd6oUvKoZPe7eAjLYneXx5viQC6tvDJE4dvN1NlkFGWsSlZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/release-script-core": "3.7.0", + "alcalzone-shared": "^4.0.1" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@alcalzone/release-script-plugin-git": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-git/-/release-script-plugin-git-3.8.0.tgz", + "integrity": "sha512-rI9EqSmvMWaNZ5xxOUBZjD4WOv1Enl+/ZxhUoTROq+K/9RYYHQaAXilGWNvnz2DYr14Q+Yx/fs54GXgAVf0scg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/release-script-core": "3.7.0", + "fs-extra": "^10.1.0" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@alcalzone/release-script-plugin-iobroker": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-iobroker/-/release-script-plugin-iobroker-3.7.2.tgz", + "integrity": "sha512-ezXFyxx2irq7my3BmlBUrzT+QHNsldR9MQYkXs7uyH6lXSkudmqq5EiDW9WckT0Cj9YqIUzoOJhhnZA9M+XTvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/release-script-core": "3.7.0", + "alcalzone-shared": "^4.0.1", + "axios": "^1.6.2", + "fs-extra": "^10.1.0", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@alcalzone/release-script-plugin-license": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-license/-/release-script-plugin-license-3.7.0.tgz", + "integrity": "sha512-HLnKHr3pc6NJJ7zAa8S/SdC305oEurnkRa9XNP5F6rgrixsxUiynBVh0tpPsVsOTdndm7fNIBRfR66IGnw0cag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/release-script-core": "3.7.0", + "fs-extra": "^10.1.0", + "tiny-glob": "^0.2.9" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@alcalzone/release-script-plugin-manual-review": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-manual-review/-/release-script-plugin-manual-review-3.7.0.tgz", + "integrity": "sha512-lU/KJHQpYhdDcYPxiR3X5BsP9O+bNYfyP8VPE5dF9fwLgTFklbGMOhF9VjdQMiZ8Cyr7fCH7ptw+oHw+UuQutQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/release-script-core": "3.7.0" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@alcalzone/release-script-plugin-package": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-package/-/release-script-plugin-package-3.7.3.tgz", + "integrity": "sha512-5oKSwbvHs9jRhARJ04eQ7xqig3a7NmPJlhtBxAqUM8+0cjs2g/V1xTxM6o8aZ09uRSdq80YGVxJm871SyszAWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/pak": "^0.10.1", + "@alcalzone/release-script-core": "3.7.0", + "alcalzone-shared": "^4.0.1", + "fs-extra": "^10.1.0", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@alcalzone/release-script-plugin-version": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-version/-/release-script-plugin-version-3.7.0.tgz", + "integrity": "sha512-030NGQeB+mglVz/58cx0WO4QiFChaSd/pz35mnOrUc9PbKWRpzisTVOt4IhCV/++YiAVibJO31NMNzvipPdx4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/release-script-core": "3.7.0", + "alcalzone-shared": "^4.0.1", + "fs-extra": "^10.1.0", + "semver": "^7.5.2", + "tiny-glob": "^0.2.9" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "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, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@iobroker/adapter-core": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@iobroker/adapter-core/-/adapter-core-3.3.2.tgz", + "integrity": "sha512-knuCX6F1JUTnY1OdHzDhwNtOoe9/RZgBziDKSzCO3E4koteY/KRqZ7gtzmoavcAokejG68S5osbEnIb4MYT3VA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@iobroker/types": ">=6.0.11" + } + }, + "node_modules/@iobroker/testing": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@iobroker/testing/-/testing-5.2.2.tgz", + "integrity": "sha512-AiYi9qziPLiNDcwZJa3tcgdSjIq9lq7z4QFvWY9w099chmKOWyvUgWzDy3S/R3L7lJ0yviOeNEdpOHtX4wTf6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/esbuild-register": "^2.5.1-1", + "@types/chai": "^4.3.20", + "@types/chai-as-promised": "^7.1.8", + "@types/mocha": "^10.0.10", + "@types/sinon": "^17.0.4", + "@types/sinon-chai": "^3.2.12", + "alcalzone-shared": "~5.0.0", + "chai": "^4.5.0", + "chai-as-promised": "^7.1.2", + "debug": "^4.4.3", + "fs-extra": "^11.3.2", + "json5": "^2.2.3", + "mocha": "^11.7.3", + "sinon": "^21.0.0", + "sinon-chai": "^3.7.0" + } + }, + "node_modules/@iobroker/testing/node_modules/alcalzone-shared": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/alcalzone-shared/-/alcalzone-shared-5.0.0.tgz", + "integrity": "sha512-X73hgVWcrIKUUB6jZgHj5flRbTft8AAoJ2MqRKEcAX1whW3OeGkxsQ6ol4nd4/rKxd1eoCRXUGW3cIhXrXU4Sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@iobroker/testing/node_modules/fs-extra": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@iobroker/types": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@iobroker/types/-/types-7.0.6.tgz", + "integrity": "sha512-hCAw/ICjxQ48cIAVBvBAkj2NMkBkYILNFsPLH5yhcAH+B57SldS/2uz37+8i+dFzn4GlMXC7X04flJK02kyIdw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.15.tgz", + "integrity": "sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==", + "license": "MIT", + "dependencies": { + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz", + "integrity": "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/json-schema": "^1.1.12", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2", + "webcrypto-core": "^1.8.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/commons/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, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "lodash.get": "^4.4.2", + "type-detect": "^4.1.0" + } + }, + "node_modules/@types/chai": { + "version": "4.3.20", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", + "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai-as-promised": { + "version": "7.1.8", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.8.tgz", + "integrity": "sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/json-bigint/-/json-bigint-1.0.4.tgz", + "integrity": "sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", + "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/readable-stream": { + "version": "4.0.22", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.22.tgz", + "integrity": "sha512-/FFhJpfCLAPwAcN3mFycNUa77ddnr8jTgF5VmSNetaemWB2cIlfCA9t0YTM3JAT0wOcv8D4tjPo7pkDhK3EJIg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/sinon": { + "version": "17.0.4", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.4.tgz", + "integrity": "sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinon-chai": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.12.tgz", + "integrity": "sha512-9y0Gflk3b0+NhQZ/oxGtaAJDvRywCa5sIyaVnounqLvmf93yBF4EgIRspePtkMs3Tr844nCclYMlcCNmLCvjuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "*", + "@types/sinon": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-15.0.1.tgz", + "integrity": "sha512-Ko2tjWJq8oozHzHV+reuvS5KYIRAokHnGbDwGh/J64LntgpbuylF74ipEL24HCyRjf9FOlBiBHWBR1RlVKsI1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/alcalzone-shared": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/alcalzone-shared/-/alcalzone-shared-4.0.8.tgz", + "integrity": "sha512-Rr0efCjNL9lw7miDvU8exL87Y42ehsLU2jUGNQUphhnlvxnTMrHeApWgoOSGZvsE2PhxC3KO7Z+VpQ/IbuV3aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/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, + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/asn1js": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", + "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.2", + "pvutils": "^1.1.3", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=12.0.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==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.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, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bl": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.4.tgz", + "integrity": "sha512-ZV/9asSuknOExbM/zPPA8z00lc1ihPKWaStHkkQrxHNeYx+yY+TmF+v80dpv2G0mv3HVXBu7ryoAsxbFFhf4eg==", + "license": "MIT", + "dependencies": { + "@types/readable-stream": "^4.0.0", + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^4.2.0" + } + }, + "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, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/broker-factory": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.10.tgz", + "integrity": "sha512-BzqK5GYFhvVFvO13uzPN0SCiOsOQuhMUbsGvTXDJMA2/N4GvIlFdxEuueE+60Zk841bBU5G3+fl2cqYEo0wgGg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "fast-unique-numbers": "^9.0.24", + "tslib": "^2.8.1", + "worker-factory": "^7.0.46" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0" + }, + "node_modules/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai-as-promised": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.2.tgz", + "integrity": "sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "check-error": "^1.0.2" + }, + "peerDependencies": { + "chai": ">= 2.1.2 < 6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commist": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz", + "integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/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" + ], + "license": "MIT", + "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==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/es-abstract": { + "version": "1.23.9", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.11.23", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.11.23.tgz", + "integrity": "sha512-iaiZZ9vUF5wJV8ob1tl+5aJTrwDczlvGP0JoMmnpC2B0ppiMCu8n8gmy5ZTGl5bcG081XBVn+U+jP+mPFm5T5Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-promise": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-7.2.1.tgz", + "integrity": "sha512-SWKjd+EuvWkYaS+uN2csvj0KoP43YTu7+phKQ5v+xw6+A0gutVX2yqCeCkC3uLCJFiPfR2dD8Es5L7yUsmvEaA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "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==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-unique-numbers": { + "version": "9.0.24", + "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.24.tgz", + "integrity": "sha512-Dv0BYn4waOWse94j16rsZ5w/0zoaCa74O3q6IZjMqaXbtT92Q+Sb6pPk+phGzD8Xh+nueQmSRI3tSCaHKidzKw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.2.0" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "license": "ISC", + "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", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "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, + "license": "MIT", + "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==", + "license": "MIT" + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "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==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/json2iob": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/json2iob/-/json2iob-2.6.21.tgz", + "integrity": "sha512-BPXkq9a8oWDdYFxt9pA7GrBGrH+Q7ZzYiJhIKNpdAaFfiFAvkeqVnMWro+cbC+SNVhVJR5nvfmMR8rvTKnDJyA==", + "license": "MIT", + "dependencies": { + "@types/json-bigint": "^1.0.4", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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.", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mocha": { + "version": "11.7.5", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", + "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", + "dev": true, + "license": "MIT", + "dependencies": { + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mqtt": { + "version": "5.14.1", + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.14.1.tgz", + "integrity": "sha512-NxkPxE70Uq3Ph7goefQa7ggSsVzHrayCD0OyxlJgITN/EbzlZN+JEPmaAZdxP1LsIT5FamDyILoQTF72W7Nnbw==", + "license": "MIT", + "dependencies": { + "@types/readable-stream": "^4.0.21", + "@types/ws": "^8.18.1", + "commist": "^3.2.0", + "concat-stream": "^2.0.0", + "debug": "^4.4.1", + "help-me": "^5.0.0", + "lru-cache": "^10.4.3", + "minimist": "^1.2.8", + "mqtt-packet": "^9.0.2", + "number-allocator": "^1.0.14", + "readable-stream": "^4.7.0", + "rfdc": "^1.4.1", + "socks": "^2.8.6", + "split2": "^4.2.0", + "worker-timers": "^8.0.23", + "ws": "^8.18.3" + }, + "bin": { + "mqtt": "build/bin/mqtt.js", + "mqtt_pub": "build/bin/pub.js", + "mqtt_sub": "build/bin/sub.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/mqtt-packet": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-9.0.2.tgz", + "integrity": "sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA==", + "license": "MIT", + "dependencies": { + "bl": "^6.0.8", + "debug": "^4.3.4", + "process-nextick-args": "^2.0.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ngeohash": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/ngeohash/-/ngeohash-0.6.3.tgz", + "integrity": "sha512-kltF0cOxgx1AbmVzKxYZaoB0aj7mOxZeHaerEtQV0YaqnkXNq26WWqMmJ6lTqShYxVRWZ/mwvvTrNeOwdslWiw==", + "license": "MIT", + "engines": { + "node": ">=v0.2.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, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/number-allocator": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", + "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.1", + "js-sdsl": "4.3.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==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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==", + "license": "MIT" + }, + "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, + "license": "ISC" + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/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.", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sinon": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.0.tgz", + "integrity": "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.5", + "@sinonjs/samsam": "^8.0.1", + "diff": "^7.0.0", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon-chai": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz", + "integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==", + "dev": true, + "license": "(BSD-2-Clause OR WTFPL)", + "peerDependencies": { + "chai": "^4.0.0", + "sinon": ">=4.0.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "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==", + "license": "MIT", + "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/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/traverse": { + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.11.tgz", + "integrity": "sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==", + "license": "MIT", + "dependencies": { + "gopd": "^1.2.0", + "typedarray.prototype.slice": "^1.0.5", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "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==", + "license": "Unlicense" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/typedarray.prototype.slice": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.5.tgz", + "integrity": "sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "math-intrinsics": "^1.1.0", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-offset": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "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==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/webcrypto-core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", + "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.13", + "@peculiar/json-schema": "^1.1.12", + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.5", + "tslib": "^2.7.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, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", + "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/worker-factory": { + "version": "7.0.46", + "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.46.tgz", + "integrity": "sha512-Sr1hq2FMgNa04UVhYQacsw+i58BtMimzDb4+CqYphZ97OfefRpURu0UZ+JxMr/H36VVJBfuVkxTK7MytsanC3w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "fast-unique-numbers": "^9.0.24", + "tslib": "^2.8.1" + } + }, + "node_modules/worker-timers": { + "version": "8.0.25", + "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.25.tgz", + "integrity": "sha512-X7Z5dmM6PlrEnaadtFQOyXHGD/IysPA3HZzaC2koqsU1VI+RvyGmjiiLiUBQixK8PH5R7ilkOzZupWskNRaXmA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "tslib": "^2.8.1", + "worker-timers-broker": "^8.0.11", + "worker-timers-worker": "^9.0.11" + } + }, + "node_modules/worker-timers-broker": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.11.tgz", + "integrity": "sha512-uwhxKru8BI9m2tsogxr2fB6POZ8LB2xH+Pu3R0mvQnAZLPgLD6K3IX4LNKPTEgTJ/j5VsuQPB+gLI1NBNKkPlg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "broker-factory": "^3.1.10", + "fast-unique-numbers": "^9.0.24", + "tslib": "^2.8.1", + "worker-timers-worker": "^9.0.11" + } + }, + "node_modules/worker-timers-worker": { + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-9.0.11.tgz", + "integrity": "sha512-pArb5xtgHWImYpXhjg1OFv7JFG0ubmccb73TFoXHXjG830fFj+16N57q9YeBnZX52dn+itRrMoJZ9HaZBVzDaA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "tslib": "^2.8.1", + "worker-factory": "^7.0.46" + } + }, + "node_modules/workerpool": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "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.1.1" + }, + "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, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json index 0436854c..7c430b5e 100644 --- a/package.json +++ b/package.json @@ -3,37 +3,51 @@ "name": "ta2k", "email": "tombox2020@gmail.com" }, + "contributors": [ + { + "name": "ta2k", + "email": "tombox2020@gmail.com" + }, + { + "name": "Sneak-L8", + "email": "git@sengler.de" + } + ], "bugs": { "url": "https://github.com/ta2k/ioBroker.vw-connect/issues" }, - "bundleDependencies": false, + "bundleDependencies": [], "dependencies": { - "@iobroker/adapter-core": "^1.0.3", - "jsdom": "^15.2.1", - "request": "^2.88.0", - "traverse": "^0.6.6", - "uuid": "^3.3.3" + "@iobroker/adapter-core": "^3.3.2", + "@peculiar/webcrypto": "^1.5.0", + "axios": "^1.13.2", + "json-bigint": "^1.0.0", + "json2iob": "^2.6.21", + "mqtt": "^5.14.1", + "ngeohash": "^0.6.3", + "qs": "^6.14.0", + "request": "^2.88.2", + "traverse": "^0.6.11", + "uuid": "^11.1.0" }, "deprecated": false, "description": "Adapter for VW Connect", "devDependencies": { - "@iobroker/testing": "^1.3.0", - "@types/chai": "^4.2.4", - "@types/chai-as-promised": "^7.1.2", - "@types/gulp": "^4.0.6", - "@types/mocha": "^5.2.7", - "@types/node": "^10.17.3", - "@types/proxyquire": "^1.3.28", - "@types/sinon": "^7.5.0", - "@types/sinon-chai": "^3.2.3", - "chai": "^4.2.0", - "chai-as-promised": "^7.1.1", - "eslint": "^6.6.0", - "gulp": "^4.0.2", - "mocha": "^6.2.2", - "proxyquire": "^2.1.3", - "sinon": "^7.5.0", - "sinon-chai": "^3.3.0" + "@alcalzone/release-script": "^3.8.0", + "@alcalzone/release-script-plugin-iobroker": "^3.7.2", + "@alcalzone/release-script-plugin-license": "^3.7.0", + "@alcalzone/release-script-plugin-manual-review": "^3.7.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "^9.39.1", + "@iobroker/testing": "^5.2.2", + "@types/node": "^22.19.1", + "eslint": "^9.39.1", + "eslint-plugin-promise": "^7.2.1", + "globals": "^16.5.0", + "typescript": "~5.9.3" + }, + "engines": { + "node": ">=10.0.0" }, "homepage": "https://github.com/ta2k/ioBroker.vw-connect", "keywords": [ @@ -43,7 +57,8 @@ "myAudi", "Skoda", "Skoda Connect", - "VW Connect" + "VW Connect", + "VW Connect Go" ], "license": "MIT", "main": "main.js", @@ -58,7 +73,10 @@ "test:integration": "mocha test/integration --exit", "test:js": "mocha --opts test/mocha.custom.opts", "test:package": "mocha test/package --exit", - "test:unit": "mocha test/unit --exit" + "test:unit": "mocha test/unit --exit", + "check": "tsc --noEmit -p tsconfig.check.json", + "translate": "translate-adapter", + "release": "release-script --all" }, - "version": "0.0.5" + "version": "0.7.15" } diff --git a/test/mocha.setup.js b/test/mocha.setup.js index b3b96311..b0c1d7b7 100644 --- a/test/mocha.setup.js +++ b/test/mocha.setup.js @@ -1,6 +1,6 @@ // Don't silently swallow unhandled rejections process.on("unhandledRejection", (e) => { - throw e; + throw e; }); // enable the should interface with sinon diff --git a/tsconfig.check.json b/tsconfig.check.json new file mode 100644 index 00000000..273f42f4 --- /dev/null +++ b/tsconfig.check.json @@ -0,0 +1,7 @@ +// Specialized tsconfig for type-checking js files +{ + "extends": "./tsconfig.json", + "compilerOptions": {}, + "include": ["**/*.js", "**/*.d.ts"], + "exclude": ["**/build", "node_modules/", "widgets/"] +} diff --git a/tsconfig.json b/tsconfig.json index 8dc229fb..a23d7e7a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,38 +1,30 @@ +// Root tsconfig to set the settings and power editor support for all TS files { - "compileOnSave": true, - "compilerOptions": { - // do not compile anything, this file is just to configure type checking - "noEmit": true, + // To update the compilation target, install a different version of @tsconfig/node... and reference it here + // https://github.com/tsconfig/bases#node-14-tsconfigjson + "extends": "@tsconfig/node14/tsconfig.json", + "compilerOptions": { + // do not compile anything, this file is just to configure type checking + "noEmit": true, - // check JS files - "allowJs": true, - "checkJs": true, + // check JS files + "allowJs": true, + "checkJs": true, - "module": "commonjs", - "moduleResolution": "node", - // this is necessary for the automatic typing of the adapter config - "resolveJsonModule": true, + // This is necessary for the automatic typing of the adapter config + "resolveJsonModule": true, - // Set this to false if you want to disable the very strict rules (not recommended) - "strict": true, - // Or enable some of those features for more fine-grained control - // "strictNullChecks": true, - // "strictPropertyInitialization": true, - // "strictBindCallApply": true, - "noImplicitAny": false, - // "noUnusedLocals": true, - // "noUnusedParameters": true, - - // Consider targetting es2017 or higher if you require the new NodeJS 8+ features - "target": "es2015", - - }, - "include": [ - "**/*.js", - "**/*.d.ts" - ], - "exclude": [ - "node_modules/**", - "admin/**" - ] -} \ No newline at end of file + // If you want to disable the stricter type checks (not recommended), uncomment the following line + // "strict": false, + // And enable some of those features for more fine-grained control + // "strictNullChecks": true, + // "strictPropertyInitialization": true, + // "strictBindCallApply": true, + "noImplicitAny": false, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + "useUnknownInCatchVariables": false + }, + "include": ["**/*.js", "**/*.d.ts"], + "exclude": ["node_modules/**", "widgets/**"] +}