diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..5484cc8f --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,40 @@ +{ + "root": true, + "env": { + "es6": true, + "node": true, + "mocha": true + }, + "extends": [ + "eslint:recommended" + ], + "plugins": [], + "rules": { + "indent": [ + "error", + 4, + { + "SwitchCase": 1 + } + ], + "no-console": "off", + "no-var": "error", + "no-trailing-spaces": "error", + "prefer-const": "error", + "quotes": [ + "error", + "single", + { + "avoidEscape": true, + "allowTemplateLiterals": true + } + ], + "semi": [ + "error", + "always" + ] + }, + "parserOptions": { + "ecmaVersion": 2018 + } +} \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..c9033264 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: Apollon77 +patreon: Apollon77 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..89561395 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Something is not working as it should +title: '' +labels: '' +assignees: '' +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '...' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots & Logfiles** +If applicable, add screenshots and logfiles to help explain your problem. + +**Versions:** + - Adapter version: + - JS-Controller version: + - Node version: + - Operating system: + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/auto-merge.yml b/.github/auto-merge.yml new file mode 100644 index 00000000..3f5fbe36 --- /dev/null +++ b/.github/auto-merge.yml @@ -0,0 +1,17 @@ +# Configure here which dependency updates should be merged automatically. +# The recommended configuration is the following: +- match: + # Only merge patches for production dependencies + dependency_type: production + update_type: "semver:patch" +- match: + # Except for security fixes, here we allow minor patches + dependency_type: production + update_type: "security:minor" +- match: + # and development dependencies can have a minor update, too + dependency_type: development + update_type: "semver:minor" + +# The syntax is based on the legacy dependabot v1 automerged_updates syntax, see: +# https://dependabot.com/docs/config-file/#automerged_updates \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..b85ff297 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: monthly + time: "04:00" + timezone: Europe/Berlin + - package-ecosystem: npm + directory: "/" + schedule: + interval: monthly + time: "04:00" + timezone: Europe/Berlin + open-pull-requests-limit: 5 + versioning-strategy: increase diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000..8a1bc210 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,79 @@ +# Configuration for probot-stale - https://github.com/probot/stale + +# Number of days of inactivity before an Issue or Pull Request becomes stale +daysUntilStale: 90 + +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. +daysUntilClose: 7 + +# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) +onlyLabels: [] + +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable +exemptLabels: + - enhancement + - security + - bug + +# Set to true to ignore issues in a project (defaults to false) +exemptProjects: true + +# Set to true to ignore issues in a milestone (defaults to false) +exemptMilestones: true + +# Set to true to ignore issues with an assignee (defaults to false) +exemptAssignees: false + +# Label to use when marking as stale +staleLabel: wontfix + +# Comment to post when marking as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs within the next 7 days. + Please check if the issue is still relevant in the most current version of the adapter + and tell us. Also check that all relevant details, logs and reproduction steps + are included and update them if needed. + Thank you for your contributions. + + Dieses Problem wurde automatisch als veraltet markiert, da es in letzter Zeit keine Aktivitäten gab. + Es wird geschlossen, wenn nicht innerhalb der nächsten 7 Tage weitere Aktivitäten stattfinden. + Bitte überprüft, ob das Problem auch in der aktuellsten Version des Adapters noch relevant ist, + und teilt uns dies mit. Überprüft auch, ob alle relevanten Details, Logs und Reproduktionsschritte + enthalten sind bzw. aktualisiert diese. + Vielen Dank für Eure Unterstützung. + +# Comment to post when removing the stale label. +# unmarkComment: > +# Your comment here. + +# Comment to post when closing a stale Issue or Pull Request. +closeComment: > + This issue has been automatically closed because of inactivity. Please open a new + issue if still relevant and make sure to include all relevant details, logs and + reproduction steps. + Thank you for your contributions. + + Dieses Problem wurde aufgrund von Inaktivität automatisch geschlossen. Bitte öffnet ein + neues Issue, falls dies noch relevant ist und stellt sicher das alle relevanten Details, + Logs und Reproduktionsschritte enthalten sind. + Vielen Dank für Eure Unterstützung. + +# Limit the number of actions per hour, from 1-30. Default is 30 +limitPerRun: 30 + +# Limit to only `issues` or `pulls` +only: issues + +# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': +# pulls: +# daysUntilStale: 30 +# markComment: > +# This pull request has been automatically marked as stale because it has not had +# recent activity. It will be closed if no further activity occurs. Thank you +# for your contributions. + +# issues: +# exemptLabels: +# - confirmed diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml new file mode 100644 index 00000000..b172f56d --- /dev/null +++ b/.github/workflows/dependabot-automerge.yml @@ -0,0 +1,22 @@ +# Automatically merge Dependabot PRs when version comparison is within the range +# that is configured in .github/auto-merge.yml + +name: Auto-Merge Dependabot PRs + +on: + pull_request_target: + +jobs: + auto-merge: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Check if PR should be auto-merged + uses: ahmadnassri/action-dependabot-auto-merge@v2 + with: + # This must be a personal access token with push access + github-token: ${{ secrets.AUTO_MERGE_TOKEN }} + # By default, squash and merge, so Github chooses nice commit messages + command: squash and merge \ 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..4e2c2713 --- /dev/null +++ b/.github/workflows/test-and-release.yml @@ -0,0 +1,113 @@ +# This is a composition of lint and test scripts +# Make sure to update this file along with the others + +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 + +permissions: + id-token: write # Required for OIDC + contents: write + pull-requests: read + +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 + + strategy: + matrix: + node-version: [22.x] + + steps: + - uses: actions/checkout@v5 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} + + - name: Install Dependencies + run: npm ci + + # - name: Run local tests + # run: npm test + + # Deploys the final package to NPM + deploy: + needs: [check-and-lint] + + # Trigger this step only when a commit on master 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/') + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [22.x] + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} + + - name: Extract the version and commit body from the tag + shell: bash + id: extract_release + # The body may be multiline, therefore newlines and % need to be escaped + run: | + VERSION="${{ github.ref }}" + VERSION=${VERSION##*/v} + echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + EOF=$(od -An -N6 -x /dev/urandom | tr -d ' ') + BODY=$(git show -s --format=%b) + echo "BODY<<$EOF" >> $GITHUB_OUTPUT + echo "$BODY" >> $GITHUB_OUTPUT + echo "$EOF" >> $GITHUB_OUTPUT + if [[ $VERSION == *"-"* ]] ; then + echo "TAG=--tag next" >> $GITHUB_OUTPUT + fi + + - name: Install Dependencies + run: npm ci + + # - name: Create a clean build + # run: npm run build + - name: Publish package to npm + run: | + npm install -g npm@latest + npm publish ${{ steps.extract_release.outputs.TAG }} + + - name: Create Github Release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + name: Release v${{ steps.extract_release.outputs.VERSION }} + draft: false + # Prerelease versions create prereleases on Github + prerelease: ${{ contains(steps.extract_release.outputs.VERSION, '-') }} + body: ${{ steps.extract_release.outputs.BODY }} diff --git a/.npmignore b/.npmignore index a5abe61d..566e6ac5 100644 --- a/.npmignore +++ b/.npmignore @@ -2,4 +2,9 @@ Gruntfile.js tasks node_modules .git -.idea \ No newline at end of file +.idea +.github +package-lock.json +.gitattributes +.travis.yml +appveyor.yml \ No newline at end of file diff --git a/.releaseconfig.json b/.releaseconfig.json new file mode 100644 index 00000000..ec65c03f --- /dev/null +++ b/.releaseconfig.json @@ -0,0 +1,3 @@ +{ + "plugins": ["license"] +} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e2b84eb1..00000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -#os: -# - linux -# - osx -language: node_js -node_js: - - "0.10" - - "0.12" - - "4" - - "6" - diff --git a/LICENSE b/LICENSE index 9e5dad4d..a44a48c1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ The MIT License (MIT) +Copyright (c) 2018-2025 Apollon77 Copyright (c) 2015-2017 soef Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.md b/README.md new file mode 100644 index 00000000..d1355507 --- /dev/null +++ b/README.md @@ -0,0 +1,508 @@ +# alexa-remote2 + +[![NPM version](http://img.shields.io/npm/v/alexa-remote2.svg)](https://www.npmjs.com/package/alexa-remote2) +[![Downloads](https://img.shields.io/npm/dm/alexa-remote2.svg)](https://www.npmjs.com/package/alexa-remote2) +![Test and Release](https://github.com/Apollon77/alexa-remote/workflows/Test%20and%20Release/badge.svg) + +Library to remote control an Alexa (Amazon Echo) device via LAN/WLAN. + +## Disclaimer +**All product and company names or logos are trademarks™ or registered® trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them or any associated subsidiaries! This personal project is maintained in spare time and has no business goal.** +**ALEXA is a trademark of AMAZON TECHNOLOGIES, INC.** + +## Troubleshooting + +### Issues when getting the cookie and tokens initially +If you still use the E-Mail or SMS based 2FA flow, then this might not work. Please update the 2FA/OTP method in the amazon settings to the current process. + +If you open the Proxy URL from a mobile device where also the Alexa App is installed on, it might be that it does not work because Amazon might open the Alexa App. So please use a device or PC where the Alexa App is not installed + +If you see a page that tells you that "alexa.amazon.xx is deprecated" and you should use the alexa app and with a QR code on it when you enter the Proxy URL" then this means that you call the proxy URL ith a different IP/Domainname then you entered in the "proxy own IP" settings, or you adjusted the IP shown in the Adapter configuration. The "proxy own IP" setting **needs to** match the IP/Domainname you use to call the proxy URL! + +### Push Connections do not connect +Sometimes it could happen that because of too many connection tries aAmazon blocks the push connection endpoint for a specific IP and "device". + +If the Push connection is never established, then you can try to use the following: +* delete all cookies, formerRegistrationData and macDms from the settings +* locale the location of the alexa-cookie2 library in your npm tree +* check if there is a file like .../alexa-cookie2/lib/formerDataStore.json - if existing please delete them +* get new cookie via proxy + +Then it should work again + +## Example + +see example folder + +## Thanks: +Partly based on [Amazon Alexa Remote Control](http://blog.loetzimmer.de/2017/10/amazon-alexa-hort-auf-die-shell-echo.html) (PLAIN shell) and [alexa-remote-control](https://github.com/thorsten-gehrig/alexa-remote-control) and [OpenHab-Addon](https://github.com/openhab/openhab2-addons/blob/f54c9b85016758ff6d271b62d255bbe41a027928/addons/binding/org.openhab.binding.amazonechocontrol) +Thank you for that work. + +## Known issues/Todos +* getNotification works, changeNotification not ... maybe change is DELETE +Create :-) (+ source for createNotification: https://github.com/noelportugal/alexa-reminders/blob/master/alexa-reminders.js#L75, and Delete/create: https://github.com/openhab/openhab2-addons/blob/f54c9b85016758ff6d271b62d255bbe41a027928/addons/binding/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/Connection.java#L829) +* docu docu docu (sorry ... will come) + +## Changelog: +### 8.0.4 (2025-11-06) +* (@Apollon77) Adjusts Authentication check to recent Amazon changes + +### 8.0.2 (2025-07-14) +* (Apollon77) Fix List updates in some cases. Disable removing details for now + +### 8.0.1 (2025-07-14) +* (Apollon77) Fix List updates in some cases. Disable removing details for now + +### 8.0.0 (2025-07-13) +* IMPORTANT: This version updated "list" and "Smarthome" APIs to the latest Amazon changes. SOme of these are breaking! Please see changelog below for details! +* (Apollon77) Added new methods as "V2" to represent the new Amazon API. The delivered data have a different structure and some values are not available anymore. + * getListsV2 + * getListV2 + * getListItemsV2 + * getSmarthomeDevicesV2 +* (Apollon77) The following methods got deprecated and try to deliver a comparable response as before by internally using the new APIs. This mapping is done on best effort and might not be 100%! + * getLists + * getList + * getListItems + * getSmarthomeDevices +* (Apollon77) deleteListItem got a new options parameter added because the version of the list to delete is required now. An Error is returned if the version is not provided. +* (Apollon77) The following methods were updated to use the new APIs in the background, so no V2 method was added: + * addListItem + * updateListItem +* (Apollon77) The following former deprecated methods were removed + * getHistory + * getActivities + +### 7.0.5 (2024-10-20) +* (danielbrunt57) Adds additional Amazon domains for HTTP/2 push connections +* (beothorn) Fixes Typing information for "names" property + +### 7.0.4 (2024-04-16) +* (Apollon77) Adjust Activity Record query to Amazon changes +* (NebzHB) fix missing amazonPage reference instead of amazon.de + +### 7.0.3 (2024-01-25) +* (Apollon77) Adjust Activity Record query to newest Amazon changes + +### 7.0.2 (2023-11-25) +* (Apollon77) Adjust some texts + +### 7.0.1 (2023-11-24) +* (Apollon77) Prevent some error/crash cases + +### 7.0.0 (2023-11-08) +* (Apollon77) Add new option "autoQueryActivityOnTrigger" to activate the automatic activity triggering (default is off!) +* (Apollon77) Optimize Timing for activity queries + +### 6.2.2 (2023-10-29) +* (Apollon77) Optimize activity detection to process all relevant entries in all cases + +### 6.2.1 (2023-10-27) +* (Apollon77) Optimize activity detection to process all relevant entries and not just the last one + +### 6.2.0 (2023-10-27) +* (Apollon77) Adjust Activity detection to work with recent Amazon changes + +### 6.1.2 (2023-09-12) +* (Apollon77) Optimize reconnection handling for push connections + +### 6.1.1 (2023-09-09) +* (Apollon77) Fix for cookie refresh check + +### 6.1.0 (2023-09-09) +* (Apollon77) Introduce optional parameter "pushDispatchHost" to specify the host for the push connection +* (Apollon77) Make sure cookie refresh timeout is valid and prevent overflow +* (Apollon77) Fix support for push connections JP/AU regions + +### 6.0.2 (2023-09-09) +* (Apollon77) Added experimental support push connections in BR region + +### 6.0.1 (2023-09-08) +* (Apollon77) Optimize reconnection timing +* (Apollon77) Added support for NA/JP and potentially AU regions + +### 6.0.0 (2023-09-08) +* IMPORTANT: Node.js 16 is now required minimum Node.js version! +* (Apollon77) Replace WS-MQTT by the new HTTP/2 push connection + +### 5.10.3 (2023-08-08) +* (Apollon77) Add new parameter usePushConnectType to specify which connectType for the push connection should be used (valid values 1, 2) + +### 5.10.1 (2022-11-30) +* (Apollon77) Do not overwrite Device details by potentially duplicate App Device details + +### 5.10.0 (2022-10-30) +* (Apollon77) Adjust deleteConversation to the new API (additional parameter is required!) + +### 5.9.0 (2022-10-30) +* (Apollon77) Add new methods for enable/disable of alarms and use in changeNotification if Alarm or MusicAlarm + +### 5.8.3 (2022-10-27) +* (Apollon77) Fix retry issues for rate limited responses from amazon +* (Apollon77) Lowercase text that sent to textcommand because other cases had issues in the past + +### 5.8.2 (2022-08-19) +* (Apollon77) Fix doNotDisturb sequence command when time string was used + +### 5.8.1 (2022-08-18) +* (Apollon77) Fix doNotDisturb sequence command + +### 5.8.0 (2022-08-09) +* (Apollon77) Allow sendSequenceCommand method to also send commands to multiple devices (when supported by the relevant command like doNotDisturb) + +### 5.7.6 (2022-08-06) +* (Apollon77) Fix Timer/Alarm creation with just providing the time and make sure next day is used when time on current day is already over + +### 5.7.5 (2022-08-04) +* (Apollon77) Fix rate limit retries + +### 5.7.4 (2022-08-03) +* (Apollon77) Update cookie library to optimize cookie handling in other regions like australia + +### 5.7.3 (2022-07-19) +* (Apollon77) Fix deviceStop sequence command + +### 5.7.2 (2022-07-19) +* (Apollon77) Fix crash case reported by Sentry + +### 5.7.1 (2022-07-18) +* (Apollon77) Fix doNotDisturb/doNotDisturbAll in sequenceCommands +* (Apollon77) Other sequence command optimizations + +### 5.7.0 (2022-07-18) +* (Apollon77) Enhance querySmarthomeDevices to directly hand over an array of request definitions to e.g. also specify properties or such +* (Apollon77) Enhance SequenceCommands: + * "deviceStop" can also be called with an array of devices + * add "deviceStopAll" to stop all devices + * add "deviceDoNotDisturb" with allowed values as follows: boolean true/false to enable/disable or a number to set a enable-duration in seconds (up to 43.200 seconds = 12h) or use "HH:MM" as string to define an hour and minute till when it is set to not disturb + * add "deviceDoNotDisturbAll" to set Do not disturb for all devices, allowed value is the same as deviceDoNotDisturb + * add "fireTVTurnOn" to turn FireTV on (value ignored) + * add "fireTVTurnOff" to turn FireTV off (value ignored) + * add "fireTVTurnOnOff" to turn a FireTV device on (value===true) or off (value===false) + * add "fireTVPauseVideo" to pause a video on a FireTV device + * add "fireTVResumeVideo" to resume a video on a FireTV device + * add "fireTVNavigateHome" to navigate home on a FireTV device +* (Apollon77) Add getFireTVEntities() method to get available FireTV devices and their supported actions +* (Apollon77) Flag Fire-TV devices as controllable and having music player support +* (Apollon77) Add several new methods to query and change settings: + * getEqualizerEnabled() + * getEqualizerRange() + * getEqualizerSettings() + * setEqualizerSettings() + * getAuxControllerState() + * setAuxControllerPortDirection() + * getPlayerQueue() + * getDeviceSettings() - generic method, ideally use the methods listed next to get specific settings + * setDeviceSettings() - generic method, ideally use the methods listed next to get specific settings + * getConnectedSpeakerOptionSetting() + * setConnectedSpeakerOptionSetting() + * getAttentionSpanSetting() + * setAttentionSpanSetting() + * getAlexaGesturesSetting() + * setAlexaGesturesSetting() + * getDisplayPowerSetting() + * setDisplayPowerSetting() + * getAdaptiveBrightnessSetting() + * setAdaptiveBrightnessSetting() + * getClockTimeFormatSetting() + * setClockTimeFormatSetting() + * getBrightnessSetting() + * setBrightnessSetting() +* (Apollon77) Allow to overwrite the used default App-Name for the Amazon App Registration +* (Apollon77) An App name included in formerRegistrationData will be used for some API requests, else the default is used +* (Apollon77) Allow to modify the API-User-Agent Postfix to specify application name and version +* (Apollon77) Enhance the handling for exceeded rate limit responses +* (Apollon77/bbindreiter) Update used User-Agent for some requests + +### 5.6.0 (2022-07-12) +* (Apollon77) Add sequence command "wait" + +### 5.5.0 (2022-07-11) +* (Apollon77) Add playAudible() to play Audible books + +### 5.4.0 (2022-07-11) +* (Apollon77) Increase timeouts for getting smart home device data +* (Apollon77) support/handle "MusicAlarm" like "Alarm" on notifications +* (Apollon77) Add convertNotificationToV2() to convert a notification object from old/queried format into one that can be used to set with new/V2 API (very pragmatic for now) + +### 5.3.0 (2022-07-09) +* (Apollon77) Adjust Alarm methods to use the new API from Amazon. Also createNotification() and parseValue4Notification() now returns the new format for Alarms +* (Apollon77) Enhance createNotification() to also support adding reccurence information +* (Apollon77) Notification objects will also have a delete method now +* (Apollon77) Notifications can be cancelled now +* (Apollon77) Adjust logging when no callback is provided again, now logs also the body +* (Apollon77) Add methods: + * getUsersMe() + * getHousehold() + * getNotificationSounds() + * getDeviceNotificationState() + * setDeviceNotificationVolume() + * setDeviceNotificationDefaultSound() + * getDeviceNotificationDefaultSound() + * getAscendingAlarmState() + * setDeviceAscendingAlarmState() + * getRoutineSkillCatalog() to request the Skill catalog that can be used in Sequence Commands + * cancelNotification() + * setNotification() and setNotificationV2() + +### 5.2.0 (2022-07-06) +* (Apollon77) Query API endpoints (including new method getEndpoints()) from Amazon on start and use this API endpoint for the calls +* (Apollon77) Enhance getDevicePreferences() to request preferences for one device +* (Apollon77) Add setDevicePreferences() to set the device preferences for a device +* (Apollon77) Add getDeviceWifiDetails() to get the Wifi definitions (including SSID and MAC) for a device +* (Apollon77) Load Device Preferences on startup and make accessible via device.preferences on the device objects +* (Apollon77) Add methods getDevicePreferences() and setDevicePreferences() to the alexa class and to the device objects +* (Apollon77) Add new Media Message "jump" (in sendMessage() method) with a mediaId as value (can be used to jump to another queue item) +* (Apollon77) Add getRoutineSoundList() to query available sound IDs for a routine +* (Apollon77) Add new command "sound" when creating/sending sequence nodes to play sounds +* (Apollon77) Add method getWholeHomeAudioGroups() to query information about the current Audio groups +* (Apollon77) Enhance sending "notification" sequence node to allow providing an object as value with keys title and text to specify the title for displaying the message too +* (Apollon77) Add setEnablementForSmarthomeDevice() to enable/disable a smart home device +* (Apollon77) Log Response with status code also when no callback is provided (but without body content) +* (Apollon77) Slightly adjust the calculated timeout when getting many smart home device values + +### 5.1.0 (2022-07-04) +* (Apollon77) Detect Rate limit exceeded response and do one automatic request retry 10s later (plus a random part) +* (Apollon77) Calculate the timeout of querySmarthomeDevices dynamically between 10s and 60s (maximum overrideable by new optional parameter) depending on the number of devices to query + +### 5.0.1 (2022-07-03) +* (Apollon77) fix type definition for sequenceCommand methods + +### 5.0.0 (2022-07-02) +* BREAKING: SequenceNode methods throws an error on invalid data instead calling callback with error as before! +* (Apollon77) Enhance multi sequence Node methods to support building node structures with sub Parallel/SerialNodes +* (Apollon77) Adjust logic to get the "global" ownerCustomerId and use Authentication response from session verification call +* (Apollon77) Add getAuthenticationDetails() method to get the Authentication response from the last successful session verification call +* (Apollon77) Add method isWsMqttConnected() to query if the WS-MQTT connection is established or not +* (Apollon77/hive) Add method stopProxyServer() to stop the proxy server pot. opened from getting a new cookie +* (Apollon77) Adjust setTuneIn method to work again for stationIds (s*) and topicIds (t*) +* (Apollon77) Do an automatic request retry with a delay of 500-1000ms (random) when error 503 is returned from Amazon services +* (Apollon77/hive) Correctly end all timers on disconnect +* (Apollon77/hive) Optimize authentication check when no cookie is set +* (Apollon77) Prevent some crash cases + +### 4.1.2 (2022-02-20) +* (TactfulElf) Allow csrf to be updated on cookie refresh and add 401 error handling + +### 4.1.1 (2021-11-13) +* (Apollon77) Prevent crash case in edge cases when unexpected WSMQTT responses are received + +### 4.1.0 (2021-11-13) +* (Apollon77) SequenceNodes created for a device are now bound to the "deviceOwnCustomer" - should help in mixed owner groups + +### 4.0.4 (2021-11-06) +* (Apollon77) Fix crash case + +### 4.0.3 (2021-10-12) +* (Apollon77) Fix crash case (Sentry IOBROKER-ALEXA2-AT) + +### 4.0.2 (2021-10-12) +* (Apollon77) Adjust Timing on Push Connection initialization +* (Apollon77) Adjust timing when matching History entries because sometimes Amazon seems to need a bit longer for new infos become available + +### 4.0.1 (2021-10-11) +* (Apollon77) Adjust call headers + +### 4.0.0 (2021-10-11) +* IMPORTANT: Node.js 10 support is dropped, supports LTS versions of Node.js starting with 12.x +* (Apollon77) Change Push connection to new signed flow +* (RodolfoSilva) Add TypeScript Type definitions + +### 3.9.0 (2021-07-30) +* (guilhermelirio) Add skill launch function +* (guilhermelirio) Add getSkills() function + +### 3.8.1 (2021-06-04) +* (bbindreiter) Set missing Accept Header + +### 3.8.0 (2021-05-11) +* (Apollon77) Always recognize "alexa" as wakeword to handle commands via the apps correctly + +### 3.7.2 (2021-04-18) +* (Apollon77) Adjust automatic Cookie Refresh interval from 7 to 4 days +* (Apollon77) Add other checks for websocket connection handling (Sentry IOBROKER-ALEXA2-32) + +### 3.7.1 (2021-02-03) +* (Apollon77) also capture tests from ASR_REPLACEMENT_TEXT and TTS_REPLACEMENT_TEXT into summary and alexaResponse + +### 3.7.0 (2021-02-03) +* (Apollon77) Implement new method to get History/Activities + * getActivities Method is still there and still triggers the Amazon route as till now. INo idea ng it still works for some users. I declared it as deprecated now + * add new method "getCustomerHistoryRecords" which uses another endpoint on Amazon side and delivers different data. The return is returned relative compatible to getActivities, so should be a drop in replacement - beside the fact that some fileds can not be provided any longer and will be simply empty! (e.g. activityStatus, deviceAccountId ...) Also in the amazon data some fields are no longer existing (e.g. domainAttributes ...) + * the event "ws-device-activity" is migrated to use the new getCustomerHistoryRecords endpoint, and so returns compatible, but not 100% the same data +* (Apollon77) Make sure to not hammer requests to Amazon in case the activity request returns an error and the received PUSH_ACTIVITY entry was not found +* (Apollon77) Detect and handle 50x error cases and handle them as if no content was returned +* (Apollon77) Enhance communication to also support gzip and deflate encoded responses because Amazon sometimes ignore requested Accept-Encoding specs. This also could improve speed + +### 3.6.0 (2021-01-28) +* (Apollon77) Adjust to new automations (Routines) route +* (Apollon77) Add getAllDeviceVolumes method +* (Apollon77) Return relationships in getSmarthomeDevices call + +### 3.5.2 (2021-01-17) +* (Apollon77) Fix potential crash issue (Sentry IOBROKER-ALEXA2-39) + +### 3.5.0 (2020-12-24) +* (Apollon77) Fix potential crash issue (Sentry IOBROKER-ALEXA2-2V) +* (FliegenKLATSCH) add cookie as new event when a new cookie is generated +* (FliegenKLATSCH) fix error code handling + +### 3.4.0 (2020-12-11) +* (Apollon77) add support for textCommand - tell an Alexa device a text as you would speak it + +### 3.3.3 (2020-12-03) +* (Apollon77) fix potential crash case (Sentry IOBROKER-ALEXA2-2K) + +### 3.3.2 (2020-11-23) +* (Apollon77) handle potential crash case (Sentry IOBROKER-ALEXA2-27) +* (Apollon77) also ignore PUSH_DEVICE_SETUP_STATE_CHANGE push messages +* (Apollon77) Optimize WSMQTT Reconnection handling for timeout cases + +### 3.3.1 (2020-07-24) +* (Apollon77) Update cookie lib to maybe be more backward compatible if login/baseUrl was changed +* (Apollon77) Increase timeout when reading routines + +### 3.3.0 (2020-07-19) +* (Apollon77) update amazon-cookie library again to optimize upgrades from earlier versions + +### 3.2.6 (2020-07-16) +* (Apollon77) update amazon-cookie library: Another try to work around Amazon changes + +### 3.2.5 (2020-07-15) +* (Apollon77) update amazon-cookie library: Another try to work around Amazon changes + +### 3.2.4 (2020-07-15) +* (Apollon77) update amazon-cookie library: Another try to work around Amazon changes + +### 3.2.3 (2020-07-13) +* (Apollon77) update amazon-cookie library to work around amazon security changes +* (Apollon77) Prevent crash on invalid data in request data (Sentry IOBROKER-ALEXA2-1A) +* (Apollon77) Make sure to handle invalid list responses correctly (Sentry IOBROKER-ALEXA2-1T) + +### 3.2.2 (2020-06-17) +* (Apollon77) Optimize Request Handling to also Handle timeouts correctly +* (Apollon77) Increase timeouts for some Smart Home calls to 30s + +### 3.2.1 (2020-06-17) +* (Apollon77) update amazon-cookie library: another optimization for Node.js 14 + +### 3.2.0 (2020-06-16) +* (Apollon77) Update Cookie Library to allow Proxy Signup again after Amazon changes +* (hive) add new commands, jokes/facts/goodnight/cleanup +* (hive) add new command curatedtts with allowed values ["goodbye", "confirmations", "goodmorning", "compliments", "birthday", "goodnight", "iamhome"] to play random curated sentences + +### 3.1.0 (2019-12-30) +* (Apollon77) remove device._orig because really big objects happened and got exceptions on deep copy using JSION.stringify + +### 3.0.3 (2019-12-28) +* (Apollon77) update cookie lib + +### 3.0.2 (2019-12-26) +* (Apollon77) Prevent some errors + +### 3.0.1 (2019-12-24) +* (Apollon77) Prevent some errors, dependency update + +### 3.0.0 (2019-12-24) +* (Apollon77) dependency updates +* (Zefau) add functionality for handling of lists +* nodejs 8.x is minimum now! + +### 2.5.5 (2019-08-09) +* (Apollon77) user different mqtt regex to hopefully support other countries better + +### 2.5.4 (2019-08-08) +* (Apollon77) make sure amazon domains are used as configured instead of "amazon.de" sometimes + +### 2.5.3 (2019-07-22) +* (Apollon77) also allow Reminders in Future >+1 day + +### 2.5.0/1 (2019-07-21) +* (Apollon77) enhance announce/ssml to allow send to multiple devices using one command + +### 2.4.0 (2019-07-21) +* (Apollon77) Finalize methods and logix to send and read and delete messages and what's needed for this + +### 2.3.7 (2019-07-06) +* (Apollon77) fix (finally) special case on authentication check + +### 2.3.6 (2019-07-05) +* (Apollon77) fix (finally) special case on authentication check + +### 2.3.5 (2019-07-01) +* (Apollon77) fix special case on authentication check + +### 2.3.4 (2019-06-25) +* (Apollon77) fix potential error on PUSH_MEDIA_PROGRESS_CHANGE push infos + +### 2.3.3 (2019-06-23) +* (Apollon77) change authentication check to hopefully better handle DNS or other "Network unavailable" errors + +### 2.3.2 (2019-06-21) +* (Apollon77) fix ssml + +### 2.3.1 (2019-06-21) +* (Apollon77) optimize handling for missing csrf cases + +### 2.3.0 (2019-06-20) +* (Apollon77) use alexa-cookie lib 2.1 with latest adoptions to Amazon changes (Cookie CSRF was missing) +* (Apollon77) fixed default cookie refresh interval +* (Apollon77) When Speak via SSML is done this is not send as card value +* (Apollon77) add PUSH_MEDIA_PROGRESS_CHANGE to known WS-MQTT topics +* (Apollon77) change WS reconnection logic to try once per minute + +### 2.2.0 (2019-01-xx) +* (Apollon77) add new sequenceCommands "calendarNext", "calendarToday", "calendarTomorrow" +* (Apollon77) fix wake word handling and history sanitizing + +### 2.1.0 (2019-01-12) +* (Apollon77) add new sequenceCommands "deviceStop", "notification", "announcement" and finally "ssml" + +### 2.0.0 (2018-12-02) +* (Apollon77) upgrade amazon-cookie lib to 2.0 + +### 1.0.3 (2018-11-17) +* (Apollon77) upgrade amazon-cookie lib +* (Apollon77) better handle ws errors and upgrade ws version to still support nodejs 6 + +### 1.0.2 (2018-11-17) +* (Apollon77) upgrade amazon-cookie lib + +### 1.0.1 (2018-11-09) +* (Apollon77) upgrade amazon-cookie lib +* (Apollon77) small fix for strange history summary content + +### 1.0.0 (2018-09-06) +* (Apollon77) polishing and finalization and make it 1.0.0 + +### 0.6.1 (2018-08-28) +* (Apollon77) rework scenes and add option to send Parallel or Sequencial commands +* (Apollon77) enhance methods for smart home device and group handling + +### 0.6.0 (2018-08-24) +* (Apollon77) several fixes and optimizations +* (Apollon77) enhance methods for smart home device and group handling + +### 0.5.2 (2018-08-16) +* (Apollon77) also allow new reminder on next day :-) + +### 0.5.0 (2018-08-16) +* (Apollon77) fix an error when getting new cookie +* (Apollon77) Add Reminder and Alarms support. +* (Apollon77) Enhance Push Connection +* (Apollon77) Added some more deviceTypes + +### 0.3.0 (2018-08-13) +* (Apollon77) Added Websocket/MQTT connection class and also initialize it when requested via alexa-remote class. +* (Apollon77) Websocet/MQTT class and also Alexa-Remote are now event emitters to be able to notify on push changes +* (Apollon77) many fixes and optimizations, changed code to be an ES6 class +* (Apollon77) reworked the "prepare" step and only initialize what's really needed and allow extra "init" methods also to update Devices, Bluetooth and such. Docs will follow +* (Apollon77) API breaking: executeAutomationRoutine is not expecting a routineId anymore, but the complete routine definition. + +### 0.1.0 +* (Apollon77) added automatic cookie renewal when email and password are provided +* (Apollon77) added authentication checks by bootstrap call (like [alexa-remote-control](https://github.com/thorsten-gehrig/alexa-remote-control)) +* (Apollon77) several fixes +* (Apollon77) added logger option + +### 0.0.x +* Versions by soef diff --git a/alexa-http2push.js b/alexa-http2push.js new file mode 100755 index 00000000..1535b29d --- /dev/null +++ b/alexa-http2push.js @@ -0,0 +1,299 @@ +const http2 = require('http2'); +const EventEmitter = require('events'); + +class AlexaHttp2Push extends EventEmitter { + + constructor(options, update_access_token) { + super(); + + this._options = options; + this.stop = false; + this.client = null; + this.stream = null; + this.pingPongInterval = null; + this.errorRetryCounter = 0; + this.reconnectTimeout = null; + this.pongTimeout = null; + this.initTimeout = null; + this.connectionActive = false; + this.access_token = null; + this.update_access_token = update_access_token; + this.inClosing = false; + } + + isConnected() { + return this.connectionActive; + } + + connect() { + this.inClosing = false; + this.update_access_token(token => { + this.access_token = token; + + let host = 'bob-dispatch-prod-eu.amazon.com'; + if (this._options.pushDispatchHost) { + host = this._options.pushDispatchHost; + } else if (this._options.amazonPage === 'amazon.com') { + host = 'bob-dispatch-prod-na.amazon.com'; + } else if (this._options.amazonPage === 'amazon.ca') { + host = 'bob-dispatch-prod-na.amazon.com'; + } else if (this._options.amazonPage === 'amazon.com.mx') { + host = 'bob-dispatch-prod-na.amazon.com'; + } else if (this._options.amazonPage === 'amazon.com.br') { + host = 'bob-dispatch-prod-na.amazon.com'; + } else if (this._options.amazonPage === 'amazon.co.jp') { + host = 'bob-dispatch-prod-fe.amazon.com'; + } else if (this._options.amazonPage === 'amazon.com.au') { + host = 'bob-dispatch-prod-fe.amazon.com'; + } else if (this._options.amazonPage === 'amazon.com.in') { + host = 'bob-dispatch-prod-fe.amazon.com'; + } else if (this._options.amazonPage === 'amazon.co.nz') { + host = 'bob-dispatch-prod-fe.amazon.com'; + } + this._options.logger && this._options.logger(`Alexa-Remote HTTP2-PUSH: Use host ${host}`); + + + const http2Options = { + ':method': 'GET', + ':path': '/v20160207/directives', + ':authority': host, + ':scheme': 'https', + 'authorization': `Bearer ${this.access_token}`, + 'accept-encoding': 'gzip', + 'user-agent': 'okhttp/4.3.2-SNAPSHOT', + }; + + const onHttp2Close = (code, reason, immediateReconnect) => { + if (this.inClosing) return; + this.inClosing = true; + if (reason) { + reason = reason.toString(); + } + try { + this.stream && this.stream.destroy(); + } catch (err) { + // ignore + } + try { + this.client && this.client.destroy(); + } catch (err) { + // ignore + } + this.client = null; + this.stream = null; + this.connectionActive = false; + this._options.logger && this._options.logger(`Alexa-Remote HTTP2-PUSH: Close: ${code}: ${reason}`); + if (this.initTimeout) { + clearTimeout(this.initTimeout); + this.initTimeout = null; + } + if (this.pingPongInterval) { + clearInterval(this.pingPongInterval); + this.pingPongInterval = null; + } + if (this.pongTimeout) { + clearTimeout(this.pongTimeout); + this.pongTimeout = null; + } + if (this.stop) { + return; + } + if (this.errorRetryCounter > 100) { + this.emit('disconnect', false, 'Too many failed retries. Check cookie and data'); + return; + } + + this.errorRetryCounter++; + + const retryDelay = (immediateReconnect || this.errorRetryCounter === 1) ? 1 : Math.min(60, this.errorRetryCounter * 5); + this._options.logger && this._options.logger(`Alexa-Remote HTTP2-PUSH: Retry Connection in ${retryDelay}s`); + if (code !== undefined || reason !== undefined) { + this.emit('disconnect', true, `Retry Connection in ${retryDelay}s (${code}: ${reason})`); + } else { + this.emit('disconnect', true, `Retry Connection in ${retryDelay}s`); + } + this.reconnectTimeout && clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null; + this.connect(); + }, retryDelay * 1000); + }; + + const onPingResponse = (resetErrorCount) => { + if (this.initTimeout) { + clearTimeout(this.initTimeout); + this.initTimeout = null; + this._options.logger && this._options.logger('Alexa-Remote HTTP2-PUSH: Initialization completed'); + this.emit('connect'); + } + if (this.pongTimeout) { + clearTimeout(this.pongTimeout); + this.pongTimeout = null; + } + this.connectionActive = true; + if (resetErrorCount) { + this.errorRetryCounter = 0; + } + }; + + try { + this.client = http2.connect(`https://${http2Options[':authority']}`, () => { + if (!this.client) { + return; + } + try { + this.stream = this.client.request(http2Options); + } catch (error) { + this._options.logger && this._options.logger(`Alexa-Remote HTTP2-PUSH: Error on Request ${error.message}`); + this.emit('error', error); + return; + } + + this.stream.on('response', async (headers) => { + if (headers[':status'] === 403) { + this._options.logger && this._options.logger('Alexa-Remote HTTP2-PUSH: Error 403 .... refresh token'); + this.update_access_token(token => { + if (token) { + this.access_token = token; + } + onHttp2Close(headers[':status'], undefined, this.errorRetryCounter < 3); + }); + } else if (headers[':status'] !== 200) { + onHttp2Close(headers[':status']); + } + }); + + this.stream.on('data', (chunk) => { + if (this.stop) { + this.stream && this.stream.end(); + this.client && this.client.close(); + return; + } + chunk = chunk.toString(); + if (chunk.startsWith('------')) { + this.client.ping(() => onPingResponse(false)); + + this.pingPongInterval = setInterval(() => { + if (!this.stream || !this.client) { + return; + } + this._options.logger && this._options.logger('Alexa-Remote HTTP2-PUSH: Send Ping'); + // console.log('SEND: ' + msg.toString('hex')); + try { + this.client.ping(() => onPingResponse(true)); + } catch (error) { + this._options.logger && this._options.logger(`Alexa-Remote HTTP2-PUSH: Error on Ping ${error.message}`); + } + + this.pongTimeout = setTimeout(() => { + this.pongTimeout = null; + this._options.logger && this._options.logger('Alexa-Remote HTTP2-PUSH: No Pong received after 30s'); + this.stream && this.stream.end(); + this.client && this.client.close(); + this.connectionActive = false; + }, 30000); + }, 180000); + + return; + } + if (chunk.startsWith('Content-Type: application/json')) { + const json_start = chunk.indexOf('{'); + const json_end = chunk.lastIndexOf('}'); + if (json_start === -1 || json_end === -1) { + this._options.logger && this._options.logger(`Alexa-Remote HTTP2-PUSH: Unexpected ResponseCould not find json in chunk: ${chunk}`); + return; + } + const message = chunk.substring(json_start, json_end + 1); + try { + const data = JSON.parse(message); + if (!data || !data.directive || !data.directive.payload || !Array.isArray(data.directive.payload.renderingUpdates)) { + this._options.logger && this._options.logger(`Alexa-Remote HTTP2-PUSH: Unexpected ResponseCould not find renderingUpdates in json: ${message}`); + return; + } + data.directive.payload.renderingUpdates.forEach(update => { + if (!update || !update.resourceMetadata) { + this._options.logger && this._options.logger(`Alexa-Remote HTTP2-PUSH: Unexpected ResponseCould not find resourceMetadata in renderingUpdates: ${message}`); + } + const dataContent = JSON.parse(update.resourceMetadata); + + const command = dataContent.command; + const payload = JSON.parse(dataContent.payload); + + this._options.logger && this._options.logger(`Alexa-Remote HTTP2-PUSH: Command ${command}: ${JSON.stringify(payload, null, 4)}`); + this.emit('command', command, payload); + }); + } catch (err) { + this.emit('unexpected-response', `Could not parse json: ${message}: ${err.message}`); + } + } + }); + + this.stream.on('close', onHttp2Close); + + this.stream.on('error', (error) => { + this._options.logger && this._options.logger(`Alexa-Remote HTTP2-PUSH: Stream-Error: ${error}`); + this.emit('error', error); + this.stream && this.stream.end(); + this.client && this.client.close(); + }); + }); + + this.client.on('close', onHttp2Close); + + this.client.on('error', (error) => { + this._options.logger && this._options.logger(`Alexa-Remote HTTP2-PUSH: Client-Error: ${error}`); + this.emit('error', error); + this.stream && this.stream.end(); + this.client && this.client.close(); + }); + } + catch (err) { + this._options.logger && this._options.logger(`Alexa-Remote HTTP2-PUSH: Error on Init ${err.message}`); + this._options.logger && this._options.logger(err.stack); + this.emit('error', err); + return; + } + this.initTimeout && clearTimeout(this.initTimeout); + + this.initTimeout = setTimeout(() => { + this._options.logger && this._options.logger('Alexa-Remote HTTP2-PUSH: Initialization not done within 30s'); + try { + this.stream && this.stream.end(); + this.client && this.client.close(); + } catch (err) { + // just make sure + } + if (this.stream || !this.reconnectTimeout) { // it seems no close was emitted so far?! + onHttp2Close(); + } + }, 30000); + }); + } + + disconnect() { + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = null; + } + if (this.pollingTimeout) { + clearTimeout(this.pollingTimeout); + this.pollingTimeout = null; + } + if (this.initTimeout) { + clearTimeout(this.initTimeout); + this.initTimeout = null; + } + this.stop = true; + if (!this.client && !this.stream) { + return; + } + try { + this.stream && this.stream.end(); + this.client && this.client.close(); + } catch (e) { + this.connectionActive && this._options.logger && this._options.logger(`Alexa-Remote HTTP2-PUSH: Disconnect error: ${e.message}`); + } + } +} + +module.exports = AlexaHttp2Push; diff --git a/alexa-remote.d.ts b/alexa-remote.d.ts new file mode 100644 index 00000000..8ed06b95 --- /dev/null +++ b/alexa-remote.d.ts @@ -0,0 +1,824 @@ +declare module "alexa-remote2" { + export type InitOptions = + | string + | Partial<{ + cookie: string; + email: string; + password: string; + proxyOnly: boolean; + proxyOwnIp: string; + proxyPort: number; + proxyLogLevel: string; + bluetooth: boolean; + logger: (...args: any[]) => void; + alexaServiceHost: string; + userAgent: string; + apiUserAgentPostfix: string + deviceAppName: string; + acceptLanguage: string; + amazonPage: string; + /** @deprecated */ + useWsMqtt: boolean; + usePushConnection: boolean; + cookieRefreshInterval: number; + macDms: { + device_private_key: string; + adp_token: string; + }; + formerRegistrationData: { + macDms: { + device_private_key: string; + adp_token: string; + }; + localCookie: string; + frc: string; + "map-md": string; + "deviceId": string; + "deviceSerial": string; + "refreshToken": string; + "tokenDate": number; + "amazonPage": string; + "csrf": string; + "deviceAppName": string; + dataVersion: number | undefined; + } + }>; + + export type AppDevice = { + deviceAccountId: string; + deviceType: string; + serialNumber: string; + }; + + export type Serial = { + accountName: string; + appDeviceList: AppDevice[]; + capabilities: string[]; + charging: string; + deviceAccountId: string; + deviceFamily: string; + deviceOwnerCustomerId: string; + deviceType: string; + deviceTypeFriendlyName: string; + essid: string; + language: string; + macAddress: string; + online: boolean; + postalCode: string; + registrationId: string; + remainingBatteryLevel: string; + serialNumber: string; + softwareVersion: string; + isControllable: boolean; + hasMusicPlayer: boolean; + isMultiroomDevice: boolean; + isMultiroomMember: boolean; + wakeWord: string; + }; + + export type CallbackWithError = (err?: Error) => void; + + export type CallbackWithErrorAndBody = (err?: Error, body?: T) => void; + + export type SerialOrName = Serial | string; + + export type SerialOrNameOrArray = SerialOrName | SerialOrName[] + + export type Value = string | number | boolean; + + export type SequenceValue = Value | { + title: string + text: string + }; + + export type Sound = { + displayName: string; + folder: string; + id: string; + providerId: string; + sampleUrl: string; + }; + + export type Status = "ON" | "OFF"; + + export type Notification = Partial<{ + alarmTime: number; + createdDate: number; + deferredAtTime: number | null; + deviceSerialNumber: string; + deviceType: string; + geoLocationTriggerData: string | null; + id: string; + musicAlarmId: string | null; + musicEntity: string | null; + notificationIndex: string; + originalDate: string; + originalTime: string; + provider: string | null; + recurringPattern: string | null; + remainingTime: number; + reminderLabel: string | null; + sound: Sound; + status: Status; + timeZoneId: string | null; + timerLabel: string | null; + triggerTime: number; + type: string; + version: string; + rRuleData: { + byMonthDays: string[], + byMonths: string[], + byWeekDays: string[], + flexibleRecurringPatternType: 'EVERY_X_WEEKS' | 'EVERY_X_MONTHS' | 'EVERY_X_DAYS' | 'EVERY_X_YEARS' | 'X_TIMES_A_WEEK' | 'X_TIMES_A_MONTH' | 'X_TIMES_A_DAY' | 'X_TIMES_A_YEAR', + frequency: 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY' | null, + intervals: number[], + nextTriggerTimes: string[], + notificationTimes: string[], + offset: number[], + recurEndDate: string | null, + recurEndTime: string | null, + recurStartDate: string | null, + recurStartTime: string | null, + recurrenceRules: string[] + }, + }>; + + type NotificationV2 = Partial<{ + trigger: { + scheduledTime: string, + recurrence: { + freq: 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY' + byDay: string[], + byMonth: string[], + interval: number + } + }, + endpointId: string, + assets: [{ + type: string, + assetId: string + }], + extensions: [] + }>; + + type GetContactsOptions = Partial<{ + includePreferencesByLevel: string; + includeNonAlexaContacts: boolean; + includeHomeGroupMembers: boolean; + bulkImportOnly: boolean; + includeBlockStatus: boolean; + dedupeMode: string; + homeGroupId: string; + }>; + + export type ListItemOptions = Partial<{ + completed: string; + listIds: string; + version: string; + value: string; + }>; + + export type GetCustomerHistoryRecordsOptions = { + startTime: number; + endTime: number; + recordType: string; + maxRecordSize: number; + }; + + export type GetConversationsOptions = Partial<{ + latest: boolean; + includeHomegroup: boolean; + unread: boolean; + modifiedSinceDate: string; + includeUserName: boolean; + }>; + + export type GetAuthenticationDetails = { + authenticated: boolean; + canAccessPrimeMusicContent: boolean; + customerEmail: string; + customerId: string; + customerName: string; + }; + + export type SmartHomeDeviceQueryEntry = { + entityId: string; + entityType: 'APPLIANCE' | 'ENTITY' | 'GROUP' + properties?: { + namespace: string; // aka interfaceName aka "Alexa.PowerController" + name: string; // e.g. "powerState" + instance?: string; + }[] + } + + export type MessageCommands = + | "play" + | "pause" + | "next" + | "previous" + | "forward" + | "rewind" + | "volume" + | "shuffle" + | "repeat" + | "jump"; + + export type SequenceNodeCommand = + | "weather" + | "traffic" + | "flashbriefing" + | "goodmorning" + | "funfact" + | "joke" + | "cleanup" + | "singasong" + | "tellstory" + | "calendarToday" + | "calendarTomorrow" + | "calendarNext" + | "textCommand" + | "curatedtts" + | "volume" + | "deviceStop" + | "deviceStopAll" + | "deviceDoNotDisturb" + | "deviceDoNotDisturbAll" + | "speak" + | "skill" + | "notification" + | "announcement" + | "ssml" + | "fireTVTurnOn" + | "fireTVTurnOff" + | "fireTVTurnOnOff" + | "fireTVPauseVideo" + | "fireTVResumeVideo" + | "fireTVNavigateHome"; + + export type SequenceType = "SerialNode" | "ParallelNode"; + + export type EntityType = "APPLIANCE" | "GROUP"; + + export type SequenceNodeDetails = { + command: SequenceNodeCommand; + value: SequenceValue; + device?: SerialOrNameOrArray; + } + + export type MultiSequenceCommand = SequenceNodeDetails | { + sequencetype: SequenceType; + nodes: MultiSequenceCommand[]; + }; + + import {EventEmitter} from 'events'; + export default class AlexaRemote extends EventEmitter { + serialNumbers: Record; + cookie?: string; + csrf?: string; + cookieData?: string; + baseUrl: string; + friendlyNames: Record; + names: Record; + lastAuthCheck: number | null; + + setCookie(_cookie: string): void; + + init(cookie: string | InitOptions, callback: CallbackWithError); + + prepare(callback: CallbackWithError): void; + + initNotifications(callback: CallbackWithError): void; + + setNotification(notification: Notification, callback: CallbackWithErrorAndBody): void + + setNotificationV2(notificationIndex: String, notification: NotificationV2, callback: CallbackWithErrorAndBody): void + + cancelNotification(notification: Notification | NotificationV2, callback: CallbackWithErrorAndBody): void + + initWakewords(callback: CallbackWithError): void; + + initDeviceState(callback: CallbackWithError): void; + + initBluetoothState(callback: CallbackWithError): void; + + /** @deprecated */ + initWsMqttConnection(): void; + initPushConnection(): void; + + /** @deprecated */ + isWsMqttConnected(): boolean; + isPushConnected(): boolean; + + getPushedActivities(): void; + + stop(): void; + + generateCookie( + email: string, + password: string, + callback: CallbackWithError + ): void; + + refreshCookie(callback: CallbackWithError): void; + + httpsGet( + noCheck: boolean, + path: string, + callback: CallbackWithError, + flags?: Record + ): void; + + httpsGetCall( + path: string, + callback: CallbackWithErrorAndBody, + flags?: Record + ): void; + + /// Public + checkAuthentication(callback: CallbackWithErrorAndBody): void; + + getUsersMe(callback: CallbackWithErrorAndBody): void; + + getHousehold(callback: CallbackWithErrorAndBody): void; + + getDevices(callback: CallbackWithErrorAndBody): void; + + getCards( + limit: number, + beforeCreationTime: string, + callback: CallbackWithErrorAndBody + ): void; + + getMedia( + serialOrName: SerialOrName, + callback: CallbackWithErrorAndBody + ): void; + + getPlayerInfo( + serialOrName: SerialOrName, + callback: CallbackWithErrorAndBody + ): void; + + /** @deprecated Use getListsV2 instead */ + getLists(callback: CallbackWithErrorAndBody): void; + getListsV2(callback: CallbackWithErrorAndBody): void; + + /** @deprecated Use getListV2 instead */ + getList(listId: string, callback: CallbackWithErrorAndBody): void; + getListV2(listId: string, callback: CallbackWithErrorAndBody): void; + + /** @deprecated Use getListItemsV2 instead */ + getListItems( + listId: string, + options: ListItemOptions, + callback: CallbackWithErrorAndBody + ): void; + getListItemsV2( + listId: string, + options: ListItemOptions, + callback: CallbackWithErrorAndBody + ): void; + + addListItem( + listId: string, + options: ListItemOptions, + callback: CallbackWithErrorAndBody + ): void; + + updateListItem( + listId: string, + listItemId: string, + options: ListItemOptions, + callback: CallbackWithErrorAndBody + ): void; + + deleteListItem( + listId: string, + listItemId: string, + options: ListItemOptions, + callback: CallbackWithErrorAndBody + ): void; + + getWakeWords(callback: CallbackWithErrorAndBody): void; + + getReminders(cached: boolean, callback: CallbackWithErrorAndBody): void; + + getNotifications(cached: boolean, callback: CallbackWithErrorAndBody): void; + + getNotificationSounds( + serialOrName: SerialOrName, + alertType: 'Timer' | 'Alarm' | CallbackWithErrorAndBody, + callback?: CallbackWithErrorAndBody + ): void + + setDeviceNotificationDefaultSound( + serialOrName: SerialOrName, + notificationType: 'Alarm', + soundId: string, + callback: CallbackWithErrorAndBody + ): void + + getDeviceNotificationDefaultSound( + serialOrName: SerialOrName, + notificationType: 'Alarm' | 'Timer', + callback: CallbackWithErrorAndBody + ): void + + getAscendingAlarmState( + serialOrName: SerialOrName | CallbackWithErrorAndBody, + callback?: CallbackWithErrorAndBody + ): void + + setDeviceAscendingAlarmState( + serialOrName: SerialOrName, + ascendingAlarmEnabled: boolean, + callback: CallbackWithErrorAndBody + ): void + + getSkills(callback: CallbackWithErrorAndBody): void; + + getRoutineSoundList(callback: CallbackWithErrorAndBody): void; + + createNotificationObject( + serialOrName: SerialOrName, + type: string, + label: string, + value: Value, + status: Status, + sound: string + ): Notification; + + convertNotificationToV2(notification: Notification): NotificationV2; + + parseValue4Notification( + notification: Notification, + value: Value + ): Notification; + + createNotification( + notification: Notification, + callback: CallbackWithErrorAndBody + ): void; + + changeNotification( + notification: Notification, + value: Value, + callback: CallbackWithErrorAndBody + ): void; + + deleteNotification( + notification: Notification, + callback: CallbackWithErrorAndBody + ): void; + + getDoNotDisturb(callback: CallbackWithErrorAndBody): void; + + getDeviceStatusList(callback: CallbackWithErrorAndBody): void; + + // alarm volume + getDeviceNotificationState( + serialOrName: SerialOrName, + callback: CallbackWithErrorAndBody + ): void; + + setDeviceNotificationVolume( + serialOrName: SerialOrName, + volumeLevel: number, + callback: CallbackWithErrorAndBody + ): void; + + getBluetooth(cached: boolean, callback: CallbackWithErrorAndBody): void; + + tuneinSearchRaw(query: string, callback: CallbackWithErrorAndBody): void; + + tuneinSearch(query: string, callback: CallbackWithErrorAndBody): void; + + setTunein( + serialOrName: SerialOrName, + guideId: string, + contentType: string, + callback: CallbackWithErrorAndBody + ): void; + + getCustomerHistoryRecords( + options: GetCustomerHistoryRecordsOptions, + callback: CallbackWithErrorAndBody + ): void; + + getAccount(callback: CallbackWithErrorAndBody): void; + + getContacts( + options: GetContactsOptions, + callback: CallbackWithErrorAndBody + ): void; + + getConversations( + options: GetConversationsOptions, + callback: CallbackWithErrorAndBody + ): void; + + connectBluetooth( + serialOrName: SerialOrName, + btAddress: string, + callback: CallbackWithErrorAndBody + ): void; + + disconnectBluetooth( + serialOrName: SerialOrName, + btAddress: string, + callback: CallbackWithErrorAndBody + ): void; + + setDoNotDisturb( + serialOrName: SerialOrName, + enabled: boolean, + callback: CallbackWithErrorAndBody + ): void; + + find(serialOrName: SerialOrName): SerialOrName | null; + + setAlarmVolume( + serialOrName: SerialOrName, + volume: number, + callback: CallbackWithErrorAndBody + ): void; + + sendCommand( + serialOrName: SerialOrName, + command: MessageCommands, + value: Value, + callback: CallbackWithErrorAndBody + ): void; + + sendMessage( + serialOrName: SerialOrName, + command: MessageCommands, + value: Value, + callback: CallbackWithErrorAndBody + ): void; + + createSequenceNode( + command: SequenceNodeCommand, + value: SequenceValue, + serialOrName: SerialOrNameOrArray, + overrideCustomerId?: string + ): void; + + buildSequenceNodeStructure( + serialOrName: SerialOrNameOrArray, + commands: MultiSequenceCommand[], + sequenceType?: SequenceType | CallbackWithErrorAndBody, + overrideCustomerId?: string + ): void; + + sendMultiSequenceCommand( + serialOrName: SerialOrNameOrArray, + commands: MultiSequenceCommand[], + sequenceType?: SequenceType | CallbackWithErrorAndBody, + overrideCustomerId?: string | CallbackWithErrorAndBody, + callback?: CallbackWithErrorAndBody + ): void; + + sendSequenceCommand( + serialOrName: SerialOrNameOrArray, + command: SequenceNodeCommand, + value: SequenceValue, + overrideCustomerId?: string | CallbackWithErrorAndBody, + callback?: CallbackWithErrorAndBody + ): void; + + getAutomationRoutines( + limit: number, + callback: CallbackWithErrorAndBody + ): void; + + executeAutomationRoutine( + serialOrName: SerialOrName, + routine: string, + callback: CallbackWithErrorAndBody + ): void; + + getRoutineSkillCatalog( + catalogId: string | CallbackWithErrorAndBody, + limit?: number | CallbackWithErrorAndBody, + callback?: CallbackWithErrorAndBody + ): void + + getMusicProviders(callback: CallbackWithErrorAndBody): void; + + playMusicProvider( + serialOrName: SerialOrName, + providerId: string, + searchPhrase: string, + callback: CallbackWithErrorAndBody + ): void; + + playAudible( + serialOrName: SerialOrName, + searchPhrase: string, + callback: CallbackWithErrorAndBody + ): void; + + sendTextMessage( + conversationId: string, + text: string, + callback: CallbackWithErrorAndBody + ): void; + + deleteConversation( + conversationId: string, + callback: CallbackWithErrorAndBody + ): void; + + setReminder( + serialOrName: SerialOrName, + timestamp: number, + label: string, + callback: CallbackWithErrorAndBody + ): void; + + getHomeGroup(callback: CallbackWithErrorAndBody): void; + + getDevicePreferences( + serialOrName: SerialOrName | CallbackWithErrorAndBody, + callback?: CallbackWithErrorAndBody + ): void; + + setDevicePreferences( + serialOrName: SerialOrName, + preferences: Record, + callback: CallbackWithErrorAndBody + ): void; + + getDeviceWifiDetails(serialOrName: SerialOrName, callback: CallbackWithErrorAndBody): void; + + getAllDoNotDisturbDeviceStatus(callback: CallbackWithErrorAndBody): void; + + getAllDeviceVolumes(callback: CallbackWithErrorAndBody): void; + + /** @deprecated Use getSmarthomeDevicesV2 instead */ + getSmarthomeDevices(callback: CallbackWithErrorAndBody): void; + getSmarthomeDevicesV2(callback: CallbackWithErrorAndBody): void; + + getSmarthomeGroups(callback: CallbackWithErrorAndBody): void; + + getSmarthomeEntities(callback: CallbackWithErrorAndBody): void; + + getSmarthomeBehaviourActionDefinitions( + callback: CallbackWithErrorAndBody + ): void; + + renameDevice( + serialOrName: SerialOrName, + newName: string, + callback: CallbackWithErrorAndBody + ): void; + + deleteSmarthomeDevice( + smarthomeDevice: string, + callback: CallbackWithErrorAndBody + ): void; + + setEnablementForSmarthomeDevice( + smarthomeDevice: string, + enabled: boolean, + callback: CallbackWithErrorAndBody + ): void; + + deleteSmarthomeGroup( + smarthomeGroup: string, + callback: CallbackWithErrorAndBody + ): void; + + deleteAllSmarthomeDevices(callback: CallbackWithErrorAndBody): void; + + discoverSmarthomeDevice(callback: CallbackWithErrorAndBody): void; + + querySmarthomeDevices( + applicanceIds: string[] | SmartHomeDeviceQueryEntry[], + entityType?: EntityType | CallbackWithErrorAndBody, + maxTimeout?: number | CallbackWithErrorAndBody, + callback?: CallbackWithErrorAndBody + ): void; + + executeSmarthomeDeviceAction( + entityIds: string[], + parameters: string[], + entityType: EntityType, + callback: CallbackWithErrorAndBody + ): void; + + unpaireBluetooth( + serialOrName: SerialOrName, + btAddress: string, + callback: CallbackWithErrorAndBody + ): void; + + deleteDevice( + serialOrName: SerialOrName, + callback: CallbackWithErrorAndBody + ): void; + + getAuthenticationDetails(): GetAuthenticationDetails; + + stopProxyServer(callback: CallbackWithError): void + + getWholeHomeAudioGroups(callback: CallbackWithErrorAndBody): void + + getEndpoints(callback: CallbackWithErrorAndBody): void + + getEqualizerEnabled(serialOrName: SerialOrName, callback: CallbackWithErrorAndBody): void + + getEqualizerRange(serialOrName: SerialOrName, callback: CallbackWithErrorAndBody): void + + getEqualizerSettings(serialOrName: SerialOrName, callback: CallbackWithErrorAndBody): void + + setEqualizerSettings( + serialOrName: SerialOrName, + bass: number, + midrange: number, + treble: number, + callback: CallbackWithErrorAndBody + ): void + + getDeviceSettings( + serialOrName: SerialOrName, + settingName: string, + callback: CallbackWithErrorAndBody + ): void; + + setDeviceSettings( + serialOrName: SerialOrName, + settingName: string, + value: any, + callback: CallbackWithErrorAndBody + ): void; + + getConnectedSpeakerOptionSetting(serialOrName: SerialOrName, callback:CallbackWithErrorAndBody): void + + setConnectedSpeakerOptionSetting( + serialOrName: SerialOrName, + speakerType: "Bluetooth" | "InternalSpeaker" | "Aux", // Aux not supported by all devices! + callback: CallbackWithErrorAndBody + ): void + + getAttentionSpanSetting(serialOrName: SerialOrName, callback: CallbackWithErrorAndBody): void; + + setAttentionSpanSetting( + serialOrName: SerialOrName, + enabled: boolean, + callback: CallbackWithErrorAndBody + ): void; + + getAlexaGesturesSetting(serialOrName: SerialOrName, callback: CallbackWithErrorAndBody): void; + + setAlexaGesturesSetting( + serialOrName: SerialOrName, + enabled: boolean, + callback: CallbackWithErrorAndBody + ): void; + + getDisplayPowerSetting(serialOrName: SerialOrName, callback: CallbackWithErrorAndBody): void; + + setDisplayPowerSetting( + serialOrName: SerialOrName, + enabled: boolean, + callback: CallbackWithErrorAndBody + ): void; + + getAdaptiveBrightnessSetting(serialOrName: SerialOrName, callback: CallbackWithErrorAndBody): void; + + setAdaptiveBrightnessSetting( + serialOrName: SerialOrName, + enabled: boolean, + callback: CallbackWithErrorAndBody + ): void; + + getClockTimeFormatSetting(serialOrName: SerialOrName, callback: CallbackWithErrorAndBody): void; + + setClockTimeFormatSetting( + serialOrName: SerialOrName, + format: "12_HOURS" | "24_HOURS", + callback: CallbackWithErrorAndBody + ): void; + + getBrightnessSetting(serialOrName: SerialOrName, callback: CallbackWithErrorAndBody): void; + + setBrightnessSetting(serialOrName: SerialOrName, brightness: number, callback: CallbackWithErrorAndBody): void; + + getAuxControllerState(serialOrName: SerialOrName, callback: CallbackWithErrorAndBody): void; + + setAuxControllerPortDirection( + serialOrName: SerialOrName, + direction: 'INPUT' | 'OUTPUT', + port: string | CallbackWithErrorAndBody, // Default is 'aux0' if not provided + callback?: CallbackWithErrorAndBody + ): void; + + getPlayerQueue( + serialOrName: SerialOrName, + size: number | CallbackWithErrorAndBody, + callback?: CallbackWithErrorAndBody + ): void; + } +} diff --git a/alexa-remote.js b/alexa-remote.js old mode 100644 new mode 100755 index b69583a2..9328bea6 --- a/alexa-remote.js +++ b/alexa-remote.js @@ -1,827 +1,3848 @@ -"use strict"; +const https = require('https'); +const querystring = require('querystring'); +const os = require('os'); +const extend = require('extend'); +//const AlexaWsMqtt = require('./alexa-wsmqtt.js'); +const AlexaHttp2Push = require('./alexa-http2push.js'); +const { v1: uuidv1 } = require('uuid'); +const zlib = require('zlib'); +const fsPath = require('path'); -let https = require('https'); +const EventEmitter = require('events'); -function AlexaRemote (cookie, csrf) { - if (!(this instanceof AlexaRemote)) return new AlexaRemote (cookie, csrf); +const officialUserAgent = 'AppleWebKit PitanguiBridge/2.2.595606.0-[HARDWARE=iPhone14_7][SOFTWARE=17.4.1][DEVICE=iPhone]'; - this.serialNumbers = {}; - this.names = {}; - this.friendlyNames = {}; - this.devices = undefined; +function _00(val) { + let s = val.toString(); + while (s.length < 2) s = `0${s}`; + return s; +} + +const SmartHomeEndpointsGraphQlQuery = `query Endpoints { + endpoints { + items { + endpointId + id + friendlyName + displayCategories { + primary { + value + } + } + legacyIdentifiers { + dmsIdentifier { + deviceType { + type + value { + text + } + } + deviceSerialNumber { + type + value { + text + } + } + } + } + legacyAppliance { + applianceId + applianceTypes + endpointTypeId + friendlyName + friendlyDescription + manufacturerName + connectedVia + modelName + entityId + actions + mergedApplianceIds + capabilities + applianceNetworkState + version + isEnabled + customerDefinedDeviceType + customerPreference + alexaDeviceIdentifierList + aliases + driverIdentity + additionalApplianceDetails + isConsentRequired + applianceKey + appliancePairs + deduplicatedPairs + entityPairs + deduplicatedAliasesByEntityId + relations + } + serialNumber { + value { + text + } + } + enablement + model { + value { + text + } + } + manufacturer { + value { + text + } + } + features { + name + operations { + name + } + } + } + } +} +`; + + +class AlexaRemote extends EventEmitter { + + constructor() { + super(); + + this.serialNumbers = {}; + this.names = {}; + this.friendlyNames = {}; + this.lastAuthCheck = null; + this.cookie = null; + this.csrf = null; + this.cookieData = null; + this.ownerCustomerId = null; + this.endpoints = null; - this.setCookie = function (_cookie, _csrf) { - cookie = _cookie; - if (_csrf) return csrf = _csrf; - let ar = cookie.match(/csrf=([^;]+)/); - if (!ar || ar.length < 2) ar = cookie.match(/csrf=([^;]+)/); - if (!csrf && ar && ar.length >= 2) { - csrf = ar[1]; + this.baseUrl = 'alexa.amazon.de'; + + this.authApiBearerToken = null; + this.authApiBearerExpiry = null; + + this.activityCsrfToken = null; + this.activityCsrfTokenExpiry = null; + this.activityCsrfTokenReferer = null; + + this.lastVolumes = {}; + this.lastEqualizer = {}; + this.lastPushedActivity = {}; + + this.activityUpdateQueue = []; + this.activityUpdateNotFoundCounter = 0; + this.activityUpdateTimeout = null; + this.activityUpdateRunning = false; + } + + setCookie(_cookie) { + if (!_cookie) return; + if (typeof _cookie === 'string') { + this.cookie = _cookie; + } + else if (_cookie && _cookie.cookie && typeof _cookie.cookie === 'string') { + this.cookie = _cookie.cookie; + } + else if (_cookie && _cookie.localCookie && typeof _cookie.localCookie === 'string') { + this.cookie = _cookie.localCookie; + this._options.formerRegistrationData = this.cookieData = _cookie; + } + else if (_cookie && _cookie.cookie && typeof _cookie.cookie === 'object') { + return this.setCookie(_cookie.cookie); } - }; - if (cookie) this.setCookie(cookie); - let baseUrl = 'layla.amazon.de'; - let self = this; - let opts = {}; + if (!this.cookie || typeof this.cookie !== 'string') return; + const ar = this.cookie.match(/csrf=([^;]+)/); + if (ar && ar.length >= 2) { + this.csrf = ar[1]; + } + if (!this.csrf) { + this.cookie = null; + return; + } + this._options.csrf = this.csrf; + this._options.cookie = this.cookie; + this.macDms = this._options.macDms = this._options.macDms || (this.cookieData && this.cookieData.macDms); + this.emit('cookie', this.cookie, this.csrf, this.macDms); + } - this.init = function (cookie, callback) { + init(cookie, callback) { if (typeof cookie === 'object') { - opts = cookie; - cookie = opts.cookie; + this._options = cookie; + if (!this._options.userAgent) { + const platform = os.platform(); + if (platform === 'win32') { + this._options.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'; + } + /*else if (platform === 'darwin') { + this._options.userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36'; + }*/ + else { + this._options.userAgent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'; + } + } + if (this._options.apiUserAgentPostfix === undefined) { + this._options.apiUserAgentPostfix = `AlexaRemote/${require(fsPath.join(__dirname, 'package.json')).version}`; + } + this._options.amazonPage = this._options.amazonPage || 'amazon.de'; + this.baseUrl = `alexa.${this._options.amazonPage}`; + + cookie = this._options.cookie; + } + this._options.logger && this._options.logger(`Alexa-Remote: Use as User-Agent: ${this._options.userAgent}`); + this._options.logger && this._options.logger(`Alexa-Remote: Use as API User-Agent Postfix: ${this._options.apiUserAgentPostfix}`); + this._options.logger && this._options.logger(`Alexa-Remote: Use as Login-Amazon-URL: ${this._options.amazonPage}`); + if (this._options.alexaServiceHost) this.baseUrl = this._options.alexaServiceHost; + this._options.logger && this._options.logger(`Alexa-Remote: Use as Base-URL: ${this.baseUrl}`); + this._options.alexaServiceHost = this.baseUrl; + if (this._options.refreshCookieInterval !== undefined && this._options.cookieRefreshInterval === undefined) { + this._options.cookieRefreshInterval = this._options.refreshCookieInterval; + delete this._options.refreshCookieInterval; + } + if (this._options.cookieRefreshInterval !== 0) { + this._options.cookieRefreshInterval = this._options.cookieRefreshInterval || 4 * 24 * 60 * 60 * 1000; // Auto Refresh after 4 days } - function helper(callback) { - if (!opts.cookie && opts.password && opts.email) { - self.generateCookie(opts.email, opts.password, function(err, res) { + if (this._options.cookieRefreshInterval < 0 || this._options.cookieRefreshInterval > 2147483646) { + this._options.cookieRefreshInterval = 4 * 24 * 60 * 60 * 1000; // Auto Refresh after 4 days + } + + const self = this; + function getCookie(callback) { + if (!self.cookie) { + self._options.logger && self._options.logger('Alexa-Remote: No cookie given, generate one'); + self._options.cookieJustCreated = true; + self.generateCookie(self._options.email, self._options.password, function(err, res) { if (!err && res) { - cookie = res.cookie; - opts.csrf = res.csrf; - opts.cookie = res.cookie; - callback (); + self.setCookie(res); // update + self.alexaCookie.stopProxyServer(); + return callback (null); } + callback(err); }); - return; } - callback(); + else { + self._options.logger && self._options.logger('Alexa-Remote: cookie was provided'); + if (self._options.formerRegistrationData) { + const tokensValidSince = Date.now() - self._options.formerRegistrationData.tokenDate; + if (tokensValidSince < 24 * 60 * 60 * 1000 && self._options.macDms && self._options.formerRegistrationData.dataVersion === 2) { + return callback(null); + } + self._options.logger && self._options.logger('Alexa-Remote: former registration data exist, try refresh'); + self._options.logger && self._options.logger(JSON.stringify(self._options.formerRegistrationData)); + self.refreshCookie(function(err, res) { + if (err || !res) { + self._options.logger && self._options.logger('Alexa-Remote: Error from refreshing cookies'); + self.cookie = null; + return getCookie(callback); // error on refresh + } + self.setCookie(res); // update + return callback(null); + }); + } + else { + callback(null); + } + } } - helper(() => { - if (opts.baseUrl) baseUrl = opts.baseUrl; - if(typeof callback === 'function') callback = callback.bind(this); - this.setCookie(cookie, opts.csrf); - if (!csrf) return callback && callback('no csrf found'); - this.prepare(callback); - }) - }; - this.prepare = function (callback) { + this.setCookie(cookie); // set initial cookie + getCookie((err) => { + if (typeof callback === 'function') callback = callback.bind(this); + if (err) { + this._options.logger && this._options.logger('Alexa-Remote: Error from retrieving cookies'); + return callback && callback(err); + } + if (!this.csrf) return callback && callback(new Error('no csrf found')); + this.checkAuthentication((authenticated, err) => { + if (err && authenticated === null) { + return callback && callback(new Error(`Error while checking Authentication: ${err}`)); + } + this._options.logger && this._options.logger(`Alexa-Remote: Authentication checked: ${authenticated}`); + if ((!authenticated && !this._options.cookieJustCreated) || !this.macDms) { + this._options.logger && !this.macDms && this._options.logger('Alexa-Remote: JWT missing, forcing a refresh ...'); + this._options.logger && this._options.logger('Alexa-Remote: Cookie was set, but authentication invalid'); + delete this._options.cookie; + delete this._options.csrf; + delete this._options.localCookie; + delete this._options.macDms; + return this.init(this._options, callback); + } + this.lastAuthCheck = new Date().getTime(); + if (this.cookieRefreshTimeout) { + clearTimeout(this.cookieRefreshTimeout); + this.cookieRefreshTimeout = null; + } + if (this._options.cookieRefreshInterval) { + this.cookieRefreshTimeout = setTimeout(() => { + this.cookieRefreshTimeout = null; + this._options.cookie = this.cookieData; + delete this._options.csrf; + this.init(this._options, callback); + }, this._options.cookieRefreshInterval); + } + this.getEndpoints((err, endpoints) => { + if (!err && endpoints && endpoints.websiteApiUrl) { + this.endpoints = endpoints; + this.baseUrl = this.endpoints.websiteApiUrl.replace(/^https?:\/\//, ''); + this._options.logger && this._options.logger(`Alexa-Remote: Change Base URL for API calls to ${this.baseUrl}`); + } else { + this._options.logger && this._options.logger(`Alexa-Remote: Could not query endpoints: ${err}`); + } + this.prepare(() => { + if (this._options.useWsMqtt || this._options.usePushConnection) { + this.initPushConnection(); + } + callback && callback(); + }); + }); + }); + }); + } + + prepare(callback) { this.getAccount((err, result) => { if (!err && result && Array.isArray(result)) { result.forEach ((account) => { if (!this.commsId) this.commsId = account.commsId; - if (!this.directId) this.directId = account.directId; + //if (!this.directedId) this.directedId = account.directedId; }); } - function getNotifications(cb) { - if (opts.notifications) return self.getNotifications(function(err, res) { cb (!err && res ? res.notifications : null) }); - cb(null); - } - - getNotifications((notifications) => { - this.getWakeWords ((err, wakeWords) => { - if (!err && wakeWords) wakeWords = wakeWords.wakeWords; - this.getDevices((err, result) => { - if (!err && result && Array.isArray(result.devices)) { - let customerIds = {}; - this.devices = result.devices; - result.devices.forEach((device) => { - this.serialNumbers [device.serialNumber] = device; - let name = device.accountName; - this.names [name] = device; - this.names [name.toLowerCase()] = device; - if (device.deviceTypeFriendlyName) { - name += ' (' + device.deviceTypeFriendlyName + ')'; - this.names [name] = device; - this.names [name.toLowerCase()] = device; - } - device._orig = JSON.parse(JSON.stringify(device)); - device._name = name; - device.sendCommand = this.sendCommand.bind(this, device); - device.setTunein = this.setTunein.bind(this, device); - device.rename = this.renameDevice.bind(this, device); - device.setDoNotDisturb = this.setDoNotDisturb.bind(this, device); - device.delete = this.deleteDevice.bind(this, device); - if (device.deviceTypeFriendlyName) this.friendlyNames[device.deviceTypeFriendlyName] = device; - if (customerIds[device.deviceOwnerCustomerId] === undefined) customerIds[device.deviceOwnerCustomerId] = 0; - customerIds[device.deviceOwnerCustomerId] += 1; - if (this.version === undefined) this.version = device.softwareVersion; - if (this.customer === undefined) this.customer = device.deviceOwnerCustomerId; - - if (notifications && Array.isArray(notifications)) { - notifications.forEach((noti) => { - if (noti.deviceSerialNumber === device.serialNumber) { - if (device.notifications === undefined) device.notifications = []; - device.notifications.push(noti); - noti.set = self.changeNotification.bind(self, noti); - } - }) - } - - if (Array.isArray (wakeWords)) wakeWords.forEach ((o) => { - if (o.deviceSerialNumber === device.serialNumber && typeof o.wakeWord === 'string') { - device.wakeWord = o.wakeWord.toLowerCase(); - } - }) - - }); - this.ownerCustomerId = Object.keys(customerIds)[0]; - } - if (opts.bluetooth) { - this.getBluetooth((err, res) => { - if (err || !res || !Array.isArray(res.bluetoothStates)) return callback && callback (err); - res.bluetoothStates.forEach((bt) => { - if (bt.pairedDeviceList && this.serialNumbers[bt.deviceSerialNumber]) { - this.serialNumbers[bt.deviceSerialNumber].bluetoothState = bt; - bt.pairedDeviceList.forEach((d) => { - bt[d.address] = d; - d.connect = function (on, cb) { - self[on ? 'connectBluetooth' : 'disconnectBluetooth'] (self.serialNumbers[bt.deviceSerialNumber], d.address, cb); - }; - d.unpaire = function (val, cb) { - self.unpaireBluetooth (device, d.address, cb); - } - }) - } - }); - callback && callback(); - }) - - } else { - callback && callback (); - } - }) - }) - }) + this.initDeviceState(() => + this.initWakewords(() => + this.initBluetoothState(() => + this.initNotifications(callback) + ) + ) + ); }); return this; - }; - - let alexaCookie; - this.generateCookie = function (email, password, callback) { - if (!alexaCookie) alexaCookie = require('alexa-cookie'); - alexaCookie(email, password, callback); - }; - - this.timestamp = this.now = function () { - return new Date().getTime(); - }; + } - this.httpsGet = function (path, callback, flags = {}) { + initNotifications(callback) { + if (!this._options.notifications) return callback && callback(); + this.getNotifications((err, res) => { + if (err || !res || !res.notifications || !Array.isArray(res.notifications)) return callback && callback(); - let options = { - host: baseUrl, - path: '', - method: 'GET', - timeout:10000, - headers: { - 'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36', - 'Content-Type': 'text/plain', - //'Content-Type': 'application/json', - //'Connection': 'keep-alive', // new - 'csrf' : csrf, - 'Cookie' : cookie + for (const serialNumber of Object.keys(this.serialNumbers)) { + this.serialNumbers[serialNumber].notifications = []; } - }; - path = path.replace(/[\n ]/g, ''); - if (!path.startsWith('/')) { - path = path.replace(/^https:\/\//, ''); - //let ar = path.match(/^([^\/]+)(\/.*$)/); - let ar = path.match(/^([^\/]+)([\/]*.*$)/); - options.host = ar[1]; - path = ar[2]; - } else { - options.host = baseUrl; - } - let time = this.now(); - path = path.replace(/%t/g, time); + res.notifications.forEach((noti) => { + const device = this.find(noti.deviceSerialNumber); + if (!device) { + //TODO: new stuff + return; + } + if (noti.alarmTime && !noti.originalTime && noti.originalDate && noti.type !== 'Timer' && !noti.rRuleData) { + const now = new Date(noti.alarmTime); + noti.originalTime = `${_00(now.getHours())}:${_00(now.getMinutes())}:${_00(now.getSeconds())}.000`; + } + noti.set = this.changeNotification.bind(this, noti); + noti.delete = this.deleteNotification.bind(this, noti); + noti.cancel = this.cancelNotification.bind(this, noti); + device.notifications.push(noti); + }); + callback && callback(); + }); + } - options.path = path; - options.method = flags.method? flags.method : flags.data ? 'POST' : 'GET'; + initWakewords(callback) { + this.getWakeWords((err, wakeWords) => { + if (err || !wakeWords || !Array.isArray(wakeWords.wakeWords)) return callback && callback(); - if (flags.headers) Object.keys(flags.headers).forEach(n => { - options.headers [n] = flags.headers[n]; + wakeWords.wakeWords.forEach((o) => { + const device = this.find(o.deviceSerialNumber); + if (!device) { + //TODO: new stuff + return; + } + if (typeof o.wakeWord === 'string') { + device.wakeWord = o.wakeWord.toLowerCase(); + } + }); + callback && callback(); }); + } - let req = https.request(options, function getDevices(res) { - let body  = ""; + initDeviceState(callback) { + this.getDevices((err, result) => { + if (!err && result && Array.isArray(result.devices)) { + this.getDevicePreferences((err, devicePrefs) => { + const devicePreferences = {}; + if (!err && devicePrefs && devicePrefs.devicePreferences && Array.isArray(devicePrefs.devicePreferences)) { + devicePrefs.devicePreferences.forEach(pref => { + devicePreferences[pref.deviceSerialNumber] = pref; + }); + } - res.on('data', function(chunk) { - body += chunk; - }); + const customerIds = {}; + const joinedDevices = []; + + result.devices.forEach((device) => { + joinedDevices.push(device); + if (device.appDeviceList && device.appDeviceList.length) { + device.appDeviceList.forEach(subDevice => { + const appDevice = Object.assign({}, device, subDevice); + appDevice.parentDeviceSerialNumber = device.serialNumber; + appDevice.appDeviceList = []; + joinedDevices.push(appDevice); + }); + } + }); - res.on('end', function() { + const processedSerialNumbers = []; + joinedDevices.forEach((device) => { + const existingDevice = this.find(device.serialNumber); + if (!existingDevice) { + this.serialNumbers[device.serialNumber] = device; + } + else { + if (device.parentDeviceSerialNumber && processedSerialNumbers.includes(device.serialNumber)) return; + device = extend(true, existingDevice, device); + } + processedSerialNumbers.push(device.serialNumber); - let ret; - if (typeof callback === 'function') { - if(!body) return callback.length >= 2 && callback('no body', null); - try { - ret = JSON.parse(body); - } catch(e) { - if (callback.length >= 2) return callback ('no JSON', body); - } - if (callback.length >= 2) return callback (null, ret); - callback(ret); - } - }); - }); + if (devicePreferences[device.serialNumber]) { + device.preferences = devicePreferences[device.serialNumber]; + } - req.on('error', function(e) { - if(typeof callback === 'function' && callback.length >= 2) { - return callback (e.message, null); + let name = device.accountName; + this.names[name] = device; + this.names[name.toLowerCase()] = device; + if (device.deviceTypeFriendlyName) { + name += ` (${device.deviceTypeFriendlyName})`; + this.names[name] = device; + this.names[name.toLowerCase()] = device; + } + //device._orig = JSON.parse(JSON.stringify(device)); + device._name = name; + device.sendCommand = this.sendCommand.bind(this, device); + device.setTunein = this.setTunein.bind(this, device); + device.playAudible = this.playAudible.bind(this, device); + device.rename = this.renameDevice.bind(this, device); + device.setDoNotDisturb = this.setDoNotDisturb.bind(this, device); + device.delete = this.deleteDevice.bind(this, device); + device.getDevicePreferences = this.getDevicePreferences.bind(this, device); + device.setDevicePreferences = this.setDevicePreferences.bind(this, device); + device.getNotificationSounds = this.getNotificationSounds.bind(this, device); + device.setDevicePreferences = this.setDevicePreferences.bind(this, device); + device.getDeviceNotificationState = this.getDeviceNotificationState.bind(this, device); + device.setDeviceNotificationVolume = this.setDeviceNotificationVolume.bind(this, device); + device.setDeviceAscendingAlarmState = this.setDeviceAscendingAlarmState.bind(this, device); + device.getDeviceNotificationDefaultSound = this.getDeviceNotificationDefaultSound.bind(this, device); + device.setDeviceNotificationDefaultSound = this.setDeviceNotificationDefaultSound.bind(this, device); + if (device.deviceTypeFriendlyName) this.friendlyNames[device.deviceTypeFriendlyName] = device; + if (customerIds[device.deviceOwnerCustomerId] === undefined) customerIds[device.deviceOwnerCustomerId] = 0; + customerIds[device.deviceOwnerCustomerId] += 1; + device.isControllable = ( + device.capabilities.includes('AUDIO_PLAYER') || + device.capabilities.includes('AMAZON_MUSIC') || + device.capabilities.includes('TUNE_IN')|| + device.capabilities.includes('AUDIBLE') || + device.deviceFamily === 'FIRE_TV' + ); + device.hasMusicPlayer = ( + device.capabilities.includes('AUDIO_PLAYER') || + device.capabilities.includes('AMAZON_MUSIC') || + device.deviceFamily === 'FIRE_TV' + ); + device.isMultiroomDevice = (device.clusterMembers.length > 0); + device.isMultiroomMember = (device.parentClusters.length > 0); + }); + //this.ownerCustomerId = Object.keys(customerIds)[0]; // this could end in the wrong one! + callback && callback(); + }); + } + else { + callback && callback(); } }); - if (flags && flags.data) { - req.write(flags.data); - } - req.end(); } -} -AlexaRemote.prototype.getDevices = function (callback) { - this.httpsGet ('/api/devices-v2/device?cached=true&_=%t', callback); -}; - -AlexaRemote.prototype.getCards = function (limit, beforeCreationTime, callback) { - if (typeof limit === 'function') { - callback = limit; - limit = 10; - } - if (typeof beforeCreationTime === 'function') { - callback = beforeCreationTime; - beforeCreationTime = '%t'; - } - if (beforeCreationTime === undefined) beforeCreationTime = '%t'; - this.httpsGet (`/api/cards?limit=${limit}&beforeCreationTime=${beforeCreationTime}000&_=%t`, callback); -}; - -AlexaRemote.prototype.getMedia = function (serialOrName, deviceType, callback) { - let dev = this.find(serialOrName, callback); - if (!dev) return; - this.httpsGet (`/api/media/state?deviceSerialNumber=${dev.serialNumber}&deviceType=${dev.deviceType}&screenWidth=1392&_=%t`, callback); -}; - -AlexaRemote.prototype.getPlayerInfo = function (serialOrName, callback) { - let dev = this.find(serialOrName, callback); - if (!dev) return; - this.httpsGet (`/api/np/player?deviceSerialNumber=${dev.serialNumber}&deviceType=${dev.deviceType}&screenWidth=1392&_=%t`, callback); -}; - -AlexaRemote.prototype.getList = function (serialOrName, listType, options, callback) { - let dev = this.find(serialOrName, callback); - if (!dev) return; - if (typeof options === 'function') { - callback = options; - options = {}; - } - this.httpsGet (` - /api/todos?size=${options.size || 100} - &startTime=${options.startTime || ''} - &endTime=${options.endTime || ''} - &completed=${options.completed || false} - &type=${listType} - &deviceSerialNumber=${dev.serialNumber} - &deviceType=${deviceType} - &_=%t`, - callback); -}; - -AlexaRemote.prototype.getLists = function (serialOrName, options, callback) { - let dev = this.find(serialOrName, callback); - if (!dev) return; - this.getList(dev, 'TASK', options, function(err, res) { - let ret = {}; - if (!err && res) { - ret.tasks = res; - } - this.getList(dev, 'SHOPPING_ITEM', options, function(err, res) { - ret.shoppingItems = res; - callback.length >= 2 ? callback(null, ret) : callback(ret); - }); - }); -}; - -AlexaRemote.prototype.getWakeWords = function (callback) { - this.httpsGet (`/api/wake-word?_=%t`, callback); -}; - -AlexaRemote.prototype.getReminders = - AlexaRemote.prototype.getNotifications = function (cached, callback) { - if (typeof cached === 'function') { - callback = cached; - cached = true; + initBluetoothState(callback) { + if (this._options.bluetooth) { + this.getBluetooth((err, res) => { + if (err || !res || !Array.isArray(res.bluetoothStates)) { + this._options.bluetooth = false; + return callback && callback (); + } + const self = this; + res.bluetoothStates.forEach((bt) => { + if (bt.pairedDeviceList && this.serialNumbers[bt.deviceSerialNumber]) { + this.serialNumbers[bt.deviceSerialNumber].bluetoothState = bt; + bt.pairedDeviceList.forEach((d) => { + bt[d.address] = d; + d.connect = function (on, cb) { + self[on ? 'connectBluetooth' : 'disconnectBluetooth'] (self.serialNumbers[bt.deviceSerialNumber], d.address, cb); + }; + d.unpaire = function (val, cb) { + self.unpaireBluetooth (self.serialNumbers[bt.deviceSerialNumber], d.address, cb); + }; + }); + } + }); + callback && callback(); + }); + } else { + callback && callback(); } - if (cached === undefined) cached = true; - this.httpsGet (`/api/notifications?cached=${cached}&_=%t`, callback); - }; - - -AlexaRemote.prototype.changeNotification = function (notification, state) { - switch (typeof state) { - case 'object': - - break; - case 'date': - notification.alarmTime = state.getTime(); - notification.originalTime = `${_00 (state.getHours ())}:${_00 (state.getMinutes ())}:${_00 (state.getSeconds ())}.000`; - break; - case 'boolean': - notification.status = state ? 'ON' : 'OFF'; - break; - case 'string': - let ar = state.split(':'); - let time = ((ar[0] * 60) + ar.length>1 ? ar[1] : 0) * 60 + ar.length > 2 ? ar[2] : 0; - let date = new Date(notification.alarmTime); - date.setHours(time / 3600); - date.setMinutes(date / 60 ^ 60); - date.setSeconds(date ^ 60); - notification.alarmTime = date.getTime(); - notification.originalTime = `${_00 (data.getHours ())}:${_00 (date.getMinutes ())}:${_00 (date.getSeconds ())}.000`; - break; - } - let flags = { - data: JSON.stringify (notification), - method: 'PUT' - }; - this.httpsGet (`https://alexa.amazon.de/api/notifications/${notification.id}`, function(err, res) { - callback(err, res); - }, - flags - ); -}; - - -AlexaRemote.prototype.getDoNotDisturb = -AlexaRemote.prototype.getDeviceStatusList = function (callback) { - this.httpsGet (`/api/dnd/device-status-list?_=%t`, callback); -}; - -// alarm volume -AlexaRemote.prototype.getDeviceNotificationState = function (serialOrName, callback) { - let dev = this.find(serialOrName, callback); - if (!dev) return; - this.httpsGet (`/api/device-notification-state/${dev.deviceType}/${dev.softwareVersion}/${dev.serialNumber}&_=%t`, callback); -}; - -AlexaRemote.prototype.getBluetooth = function (cached, callback) { - if (typeof cached === 'function') { - callback = cached; - cached = true; - } - if (cached === undefined) cached = true; - this.httpsGet (`/api/bluetooth?cached=${cached}&_=%t`, callback); -}; - -AlexaRemote.prototype.tuneinSearchRaw = function (query, callback) { - this.httpsGet (`/api/tunein/search?query=${query}&mediaOwnerCustomerId=${this.ownerCustomerId}&_=%t`, callback); -}; - -AlexaRemote.prototype.tuneinSearch = function (query, callback) { - query = querystring.escape(query); - this.tuneinSearchRaw(query, callback); -}; - - -AlexaRemote.prototype.setTunein = function (serialOrName, guideId, contentType, callback) { - if (typeof contentType === 'function') { - callback = contentType; - contentType = 'station'; - } - let dev = this.find(serialOrName, callback); - if (!dev) return; - this.httpsGet (`/api/tunein/queue-and-play - ?deviceSerialNumber=${dev.serialNumber} - &deviceType=${dev.deviceType} - &guideId=${guideId} - &contentType=${contentType} - &callSign= - &mediaOwnerCustomerId=${this.ownerCustomerId}`, - callback, - { method: 'POST' }); -}; - -AlexaRemote.prototype.getHistory = -AlexaRemote.prototype.getActivities = function (options, callback) { - if (typeof options === 'function') { - callback = options; - options = {}; - } - this.httpsGet (`/api/activities` + - `?startTime=${options.startTime || ''}` + - `&size=${options.size || 1}` + - `&offset=${options.offset || 1}`, - (err, result) => { - if (err || !result) return callback.length >= 2 && callback(err, result); - - let ret = []; - for (let r=0; r= 2 ? callback (err, ret) : callback(ret); - } - ); -}; - -AlexaRemote.prototype.getAccount = function (callback) { - this.httpsGet (`https://alexa-comms-mobile-service.amazon.com/accounts`, callback); -}; - -AlexaRemote.prototype.getConversations = function (options, callback) { - if (typeof options === 'function') { - callback = options; - options = undefined; - } - if (opntions === undefined) options = {}; - if (options.latest === undefined) options.latest = true; - if (options.includeHomegroup === undefined) options.includeHomegroup = true; - if (options.unread === undefined) options.unread = false; - if (options.modifiedSinceDate === undefined) options.modifiedSinceDate = '1970-01-01T00:00:00.000Z'; - if (options.includeUserName === undefined) options.includeUserName = true; - - this.httpsGet ( - `https://alexa-comms-mobile-service.amazon.com/users/${this.commsId}/conversations - ?latest=${options.latest} - &includeHomegroup=${options.includeHomegroup} - &unread=${options.unread} - &modifiedSinceDate=${options.modifiedSinceDate} - &includeUserName=${includeUserName}true`, - function (err, result) { - callback (err, result); - }); -}; - -AlexaRemote.prototype.connectBluetooth = function (serialOrName, btAddress, callback) { - let dev = this.find(serialOrName, callback); - if (!dev) return; - let flags = { - data: JSON.stringify({ bluetoothDeviceAddress: btAddress}), - method: 'POST' - }; - this.httpsGet (`/api/bluetooth/pair-sink/${dev.deviceType}/${dev.serialNumber}&_=%t`, callback, flags); -}; - -AlexaRemote.prototype.connectBluetooth = function (serialOrName, btAddress, callback) { - let dev = this.find(serialOrName, callback); - if (!dev) return; - this.httpsGet (`/api/bluetooth/pair-sink/${dev.deviceType}/${dev.serialNumber}`, - callback, { - data: JSON.stringify({ bluetoothDeviceAddress: btAddress}), - method: 'POST' - }); -}; - -AlexaRemote.prototype.disconnectBluetooth = function (serialOrName, btAddress, callback) { - let dev = this.find(serialOrName, callback); - if (!dev) return; - let flags = { - data: JSON.stringify({ bluetoothDeviceAddress: btAddress}), - method: 'POST' - }; - this.httpsGet (`/api/bluetooth/disconnect-sink/${dev.deviceType}/${dev.serialNumber}`, callback, flags); -}; - -AlexaRemote.prototype.setDoNotDisturb = function (serialOrName, enabled, callback) { - let dev = this.find(serialOrName, callback); - if (!dev) return; - let flags = { - data: JSON.stringify({ - deviceSerialNumber: dev.serialNumber, - deviceType: dev.deviceType, - enabled: enabled - }), - method: 'PUT' - }; - this.httpsGet (`//api/dnd/status`, callback, flags); -}; - -AlexaRemote.prototype.find = function(serialOrName, callback) { - if (typeof serialOrName === 'object') return serialOrName; - let dev = this.serialNumbers[serialOrName]; - if (dev !== undefined) return dev; - dev = this.names[serialOrName]; - if (!dev) dev = this.names [serialOrName.toLowerCase()]; - if (!dev) dev = this.friendlyNames[serialOrName]; - if (!dev) callback.length >= 2 && callback('wrong serial or name', null); - return dev; -}; - -AlexaRemote.prototype.setAlarmVolume = function (serialOrName, volume, callback) { - let dev = this.find(serialOrName, callback); - if (!dev) return; - let flags = { - data: JSON.stringify ({ - deviceSerialNumber: dev.serialNumber, - deviceType: dev.deviceType, - softwareVersion: dev.softwareVersion, - volumeLevel: volume - }), - method: 'PUT' - }; - this.httpsGet (`/api/device-notification-state/${dev.deviceType}/${this.version}/${dev.serialNumber}&_=%t`, callback, flags); -}; - -AlexaRemote.prototype.sendCommand = -AlexaRemote.prototype.sendMessage = function (serialOrName, command, value, callback) { - let dev = this.find(serialOrName, callback); - if (!dev) return; - - let o = { contentFocusClientId: null }; - switch (command) { - case 'play': - case 'pause': - case 'next': - case 'previous': - case 'forward': - case 'rewind': - o.type = command.substr(0, 1).toUpperCase() + command.substr(1) + 'Command'; - break; - case 'volume': - o.type = 'VolumeLevelCommand'; - o.volumeLevel = ~~value; - break; - case 'shuffle': - o.shuffle = value === 'on'; - break; - case 'repeat': - o.repeat = value === 'on'; - break; - default: - return; } - this.httpsGet (`/api/np/command?deviceSerialNumber=${dev.serialNumber}&deviceType=${dev.deviceType}`, - callback, - { - method: 'POST', - data: JSON.stringify(o), - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' - } - // Content-Type: application/x-www-form-urlencoded; - // charset=UTF-8",#\r\n - // Referer: https://alexa.amazon.de/spa/index.html' + stopProxyServer(callback) { + if (!this.alexaCookie) { + return callback && callback(); } - ); -}; + this.alexaCookie.stopProxyServer(callback); + } + + /** @deprecated */ + isWsMqttConnected() { + return this.isPushConnected(); + } + + isPushConnected() { + return this.alexahttp2Push && this.alexahttp2Push.isConnected(); + } + + /** + * @deprecated + */ + initWsMqttConnection() { + return this.initPushConnection(); + } + + simulateActivity(deviceSerialNumber, destinationUserId) { + if (!this._options.autoQueryActivityOnTrigger) return; + if (this.activityUpdateTimeout && this.activityUpdateQueue.some(entry => entry.deviceSerialNumber === deviceSerialNumber && entry.destinationUserId === destinationUserId)) return; + this._options.logger && this._options.logger(`Alexa-Remote: Simulate activity for ${deviceSerialNumber} with destinationUserId ${destinationUserId} ... fetch in 3s`); -AlexaRemote.prototype.sendTextMessage = function (conversationId, text, callback) { - let o = { - type: 'message/text', - payload: { - text: text + if (this.activityUpdateTimeout) { + clearTimeout(this.activityUpdateTimeout); + this.activityUpdateTimeout = null; } - }; + this.activityUpdateQueue.push({ + deviceSerialNumber: deviceSerialNumber, + destinationUserId: destinationUserId, + activityTimestamp: Date.now() + }); + this.activityUpdateTimeout = setTimeout(() => { + this.activityUpdateTimeout = null; + this.getPushedActivities(); + }, 4000); + } - this.httpsGet (`https://alexa-comms-mobile-service.amazon.com/users/${this.commsId}/conversations/${conversationId}/messages`, - callback, - { - method: 'POST', - data: JSON.stringify (o), - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' - } - // Content-Type: application/x-www-form-urlencoded; - // charset=UTF-8",#\r\n - // Referer: https://alexa.amazon.de/spa/index.html' + initPushConnection() { + if (this.alexahttp2Push) { + this.alexahttp2Push.removeAllListeners(); + this.alexahttp2Push.disconnect(); + this.alexahttp2Push = null; } - ); -}; + if (!this.authApiBearerToken) { + this.updateApiBearerToken((err) => { + if (err) { + this._options.logger && this._options.logger('Alexa-Remote: Initializing WS-MQTT Push Connection failed because no Access-Token available!'); + } else { + return this.initPushConnection(); + } + }); + return; + } + this.alexahttp2Push = new AlexaHttp2Push(this._options, (callback) => { + this._options.logger && this._options.logger('Alexa-Remote: Update access token ...'); + this.updateApiBearerToken((err) => { + if (err) { + this._options.logger && this._options.logger('Alexa-Remote: Initializing WS-MQTT Push Connection failed because no Access-Token available!'); + callback(null); + } else { + callback(this.authApiBearerToken); + } + }); + }); + if (!this.alexahttp2Push) return; + + this._options.logger && this._options.logger('Alexa-Remote: Initialize WS-MQTT Push Connection'); + this.alexahttp2Push.on('disconnect', (retries, msg) => { + this.emit('ws-disconnect', retries, msg); + }); + this.alexahttp2Push.on('error', (error) => { + this.emit('ws-error', error); + }); + this.alexahttp2Push.on('connect', () => { + this.emit('ws-connect'); + }); + this.alexahttp2Push.on('unknown', (incomingMsg) => { + this.emit('ws-unknown-message', incomingMsg); + }); + this.alexahttp2Push.on('command', (command, payload) => { + + this.emit('command', { 'command': command, 'payload': payload }); + + switch(command) { + case 'PUSH_DOPPLER_CONNECTION_CHANGE': + /* + { + 'destinationUserId': 'A3NSX4MMJVG96V', + 'dopplerId': { + 'deviceSerialNumber': 'c6c113ab49ff498185aa1ee5eb50cd73', + 'deviceType': 'A3H674413M2EKB' + }, + 'dopplerConnectionState': 'OFFLINE' / 'ONLINE' + } + */ + this.emit('ws-device-connection-change', { + destinationUserId: payload.destinationUserId, + deviceSerialNumber: payload.dopplerId.deviceSerialNumber, + deviceType: payload.dopplerId.deviceType, + connectionState: payload.dopplerConnectionState + }); + return; + case 'PUSH_BLUETOOTH_STATE_CHANGE': + /* + { + 'destinationUserId': 'A3NSX4MMJVG96V', + 'dopplerId': { + 'deviceSerialNumber': 'G090LF09643202VS', + 'deviceType': 'A3S5BH2HU6VAYF' + }, + 'bluetoothEvent': 'DEVICE_DISCONNECTED', + 'bluetoothEventPayload': null, + 'bluetoothEventSuccess': false/true + } + */ + this.emit('ws-bluetooth-state-change', { + destinationUserId: payload.destinationUserId, + deviceSerialNumber: payload.dopplerId.deviceSerialNumber, + deviceType: payload.dopplerId.deviceType, + bluetoothEvent: payload.bluetoothEvent, + bluetoothEventPayload: payload.bluetoothEventPayload, + bluetoothEventSuccess: payload.bluetoothEventSuccess + }); + return; + case 'PUSH_AUDIO_PLAYER_STATE': + /* + { + 'destinationUserId': 'A3NSX4MMJVG96V', + 'mediaReferenceId': '2868373f-058d-464c-aac4-12e12aa58883:2', + 'dopplerId': { + 'deviceSerialNumber': 'G090LF09643202VS', + 'deviceType': 'A3S5BH2HU6VAYF' + }, + 'error': false, + 'audioPlayerState': 'INTERRUPTED', / 'FINISHED' / 'PLAYING' + 'errorMessage': null + } + */ + this.emit('ws-audio-player-state-change', { + destinationUserId: payload.destinationUserId, + deviceSerialNumber: payload.dopplerId.deviceSerialNumber, + deviceType: payload.dopplerId.deviceType, + mediaReferenceId: payload.mediaReferenceId, + audioPlayerState: payload.audioPlayerState, // 'INTERRUPTED', / 'FINISHED' / 'PLAYING' + error: payload.error, + errorMessage: payload.errorMessage + }); + return; + case 'PUSH_MEDIA_QUEUE_CHANGE': + /* + { + 'destinationUserId': 'A3NSX4MMJVG96V', + 'changeType': 'NEW_QUEUE', + 'playBackOrder': null, + 'trackOrderChanged': false, + 'loopMode': null, + 'dopplerId': { + 'deviceSerialNumber': 'G090LF09643202VS', + 'deviceType': 'A3S5BH2HU6VAYF' + } + } + */ + this.emit('ws-media-queue-change', { + destinationUserId: payload.destinationUserId, + deviceSerialNumber: payload.dopplerId.deviceSerialNumber, + deviceType: payload.dopplerId.deviceType, + changeType: payload.changeType, + playBackOrder: payload.playBackOrder, + trackOrderChanged: payload.trackOrderChanged, + loopMode: payload.loopMode + }); + return; + case 'PUSH_MEDIA_CHANGE': + /* + { + 'destinationUserId': 'A3NT1OXG4QHVPX', + 'mediaReferenceId': '71c4d721-0e94-4b3e-b912-e1f27fcebba1:1', + 'dopplerId': { + 'deviceSerialNumber': 'G000JN0573370K82', + 'deviceType': 'A1NL4BVLQ4L3N3' + } + } + */ + this.emit('ws-media-change', { + destinationUserId: payload.destinationUserId, + deviceSerialNumber: payload.dopplerId.deviceSerialNumber, + deviceType: payload.dopplerId.deviceType, + mediaReferenceId: payload.mediaReferenceId + }); + return; + case 'PUSH_MEDIA_PROGRESS_CHANGE': + /* + { + "destinationUserId": "A2Z2SH760RV43M", + "progress": { + "mediaProgress": 899459, + "mediaLength": 0 + }, + "dopplerId": { + "deviceSerialNumber": "G2A0V7048513067J", + "deviceType": "A18O6U1UQFJ0XK" + }, + "mediaReferenceId": "c4a72dbe-ef6b-42b7-8104-0766aa32386f:1" + } + */ + this.emit('ws-media-progress-change', { + destinationUserId: payload.destinationUserId, + deviceSerialNumber: payload.dopplerId.deviceSerialNumber, + deviceType: payload.dopplerId.deviceType, + mediaReferenceId: payload.mediaReferenceId, + mediaProgress: payload.progress ? payload.progress.mediaProgress : null, + mediaLength: payload.progress ? payload.progress.mediaLength : null + }); + return; + case 'PUSH_VOLUME_CHANGE': + /* + { + 'destinationUserId': 'A3NSX4MMJVG96V', + 'dopplerId': { + 'deviceSerialNumber': 'c6c113ab49ff498185aa1ee5eb50cd73', + 'deviceType': 'A3H674413M2EKB' + }, + 'isMuted': false, + 'volumeSetting': 50 + } + */ + if ( + !this.lastVolumes[payload.dopplerId.deviceSerialNumber] || + ( + this.lastVolumes[payload.dopplerId.deviceSerialNumber].volumeSetting === payload.volumeSetting && + this.lastVolumes[payload.dopplerId.deviceSerialNumber].isMuted === payload.isMuted + ) || + ( + this.lastEqualizer[payload.dopplerId.deviceSerialNumber] && + Math.abs(Date.now() - this.lastEqualizer[payload.dopplerId.deviceSerialNumber].updated) < 2000 + ) + ) { + this.simulateActivity(payload.dopplerId.deviceSerialNumber, payload.destinationUserId); + } + this.lastVolumes[payload.dopplerId.deviceSerialNumber] = { + volumeSetting: payload.volumeSetting, + isMuted: payload.isMuted, + updated: Date.now() + }; + + this.emit('ws-volume-change', { + destinationUserId: payload.destinationUserId, + deviceSerialNumber: payload.dopplerId.deviceSerialNumber, + deviceType: payload.dopplerId.deviceType, + isMuted: payload.isMuted, + volume: payload.volumeSetting + }); + return; + case 'PUSH_CONTENT_FOCUS_CHANGE': + /* + { + 'destinationUserId': 'A3NSX4MMJVG96V', + 'clientId': '{value=Dee-Domain-Music}', + 'dopplerId': { + 'deviceSerialNumber': 'G090LF09643202VS', + 'deviceType': 'A3S5BH2HU6VAYF' + }, + 'deviceComponent': 'com.amazon.dee.device.capability.audioplayer.AudioPlayer' + } + */ + this.emit('ws-content-focus-change', { + destinationUserId: payload.destinationUserId, + deviceSerialNumber: payload.dopplerId.deviceSerialNumber, + deviceType: payload.dopplerId.deviceType, + deviceComponent: payload.deviceComponent + }); + return; + case 'PUSH_EQUALIZER_STATE_CHANGE': + /* + { + 'destinationUserId': 'A3NSX4MMJVG96V', + 'bass': 0, + 'treble': 0, + 'dopplerId': { + 'deviceSerialNumber': 'G090LA09751707NU', + 'deviceType': 'A2M35JJZWCQOMZ' + }, + 'midrange': 0 + } + */ + if ( + !this.lastEqualizer[payload.dopplerId.deviceSerialNumber] || + ( + this.lastEqualizer[payload.dopplerId.deviceSerialNumber].bass === payload.bass && + this.lastEqualizer[payload.dopplerId.deviceSerialNumber].treble === payload.treble && + this.lastEqualizer[payload.dopplerId.deviceSerialNumber].midrange === payload.midrange + ) || + ( + this.lastVolumes[payload.dopplerId.deviceSerialNumber] && + Math.abs(Date.now() - this.lastVolumes[payload.dopplerId.deviceSerialNumber].updated) < 2000 + ) + ) { + this.simulateActivity(payload.dopplerId.deviceSerialNumber, payload.destinationUserId); + } + this.lastEqualizer[payload.dopplerId.deviceSerialNumber] = { + bass: payload.bass, + treble: payload.treble, + midrange: payload.midrange, + updated: Date.now() + }; + + this.emit('ws-equilizer-state-change', { + destinationUserId: payload.destinationUserId, + deviceSerialNumber: payload.dopplerId.deviceSerialNumber, + deviceType: payload.dopplerId.deviceType, + bass: payload.bass, + treble: payload.treble, + midrange: payload.midrange + }); + return; + case 'PUSH_NOTIFICATION_CHANGE': + /* + { + 'destinationUserId': 'A3NSX4MMJVG96V', + 'dopplerId': { + 'deviceSerialNumber': 'G090LF09643202VS', + 'deviceType': 'A3S5BH2HU6VAYF' + }, + 'eventType': 'UPDATE', + 'notificationId': 'd676d954-3c34-3559-83ac-606754ff6ec1', + 'notificationVersion': 2 + } + */ + this.emit('ws-notification-change', { + destinationUserId: payload.destinationUserId, + deviceSerialNumber: payload.dopplerId.deviceSerialNumber, + deviceType: payload.dopplerId.deviceType, + eventType: payload.eventType, + notificationId: payload.notificationId, + notificationVersion: payload.notificationVersion + }); + return; + + case 'PUSH_ACTIVITY': + /* + { + 'destinationUserId': 'A3NSX4MMJVG96V', + 'key': { + 'entryId': '1533932315288#A3S5BH2HU6VAYF#G090LF09643202VS', + 'registeredUserId': 'A3NSX4MMJVG96V' + }, + 'timestamp': 1533932316865 + } -AlexaRemote.prototype.setList = function (serialOrName, listType, value, callback) { - let dev = this.find(serialOrName, callback); - if (!dev) return; - let o = { - type: listType, - text: value, - createdDate: this.now(), - complete: false, - deleted: false - }; + { + '_disambiguationId': null, + 'activityStatus': 'SUCCESS', // DISCARDED_NON_DEVICE_DIRECTED_INTENT // FAULT + 'creationTimestamp': 1533932315288, + 'description': '{\'summary\':\'spiel Mike Oldfield von meine bibliothek\',\'firstUtteranceId\':\'TextClient:1.0/2018/08/10/20/G090LF09643202VS/18:35::TNIH_2V.cb0c133b-3f90-4f7f-a052-3d105529f423LPM\',\'firstStreamId\':\'TextClient:1.0/2018/08/10/20/G090LF09643202VS/18:35::TNIH_2V.cb0c133b-3f90-4f7f-a052-3d105529f423LPM\'}', + 'domainAttributes': '{\'disambiguated\':false,\'nBestList\':[{\'entryType\':\'PlayMusic\',\'mediaOwnerCustomerId\':\'A3NSX4MMJVG96V\',\'playQueuePrime\':false,\'marketplace\':\'A1PA6795UKMFR9\',\'imageURL\':\'https://album-art-storage-eu.s3.amazonaws.com/93fff3ba94e25a666e300facd1ede29bf84e6e17083dc7e60c6074a77de71a1e_256x256.jpg?response-content-type=image%2Fjpeg&x-amz-security-token=FQoGZXIvYXdzEP3%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDInhZqxchOhE%2FCQ3bSKrAWGE9OKTrShkN7rSKEYzYXH486T6c%2Bmcbru4RGEGu9Sq%2BL%2FpG5o2EWsHnRULSM4cpreC1KG%2BIfzo8nuskQk8fklDgIyrK%2B%2B%2BFUm7rxmTKWBjavbKQxEtrnQATgo7%2FghmztEmXC5r742uvyUyAjZcZ4chCezxa%2Fkbr00QTv1HX18Hj5%2FK4cgItr5Kyv2bfmFTZ2Jlvr8IbAQn0X0my1XpGJyjUuW8IGIPhqiCQyi627fbBQ%3D%3D&AWSAccessKeyId=ASIAZZLLX6KM4MGDNROA&Expires=1533935916&Signature=OozE%2FmJbIVVvK2CRhpa2VJPYudE%3D\',\'artistName\':\'Mike Oldfield\',\'serviceName\':\'CLOUD_PLAYER\',\'isAllSongs\':true,\'isPrime\':false}]}', + 'domainType': null, + 'feedbackAttributes': null, + 'id': 'A3NSX4MMJVG96V#1533932315288#A3S5BH2HU6VAYF#G090LF09643202VS', + 'intentType': null, + 'providerInfoDescription': null, + 'registeredCustomerId': 'A3NSX4MMJVG96V', + 'sourceActiveUsers': null, + 'sourceDeviceIds': [{ + 'deviceAccountId': null, + 'deviceType': 'A3S5BH2HU6VAYF', + 'serialNumber': 'G090LF09643202VS' + }], + 'utteranceId': 'TextClient:1.0/2018/08/10/20/G090LF09643202VS/18:35::TNIH_2V.cb0c133b-3f90-4f7f-a052-3d105529f423LPM', + 'version': 1 + } + */ + this.activityUpdateQueue.push(payload); + if (this.activityUpdateTimeout) { + clearTimeout(this.activityUpdateTimeout); + this.activityUpdateTimeout = null; + } + this.activityUpdateTimeout = setTimeout(() => { + this.activityUpdateTimeout = null; + this.getPushedActivities(); + }, 200); + return; + + case 'PUSH_TODO_CHANGE': // does not exist? + case 'PUSH_LIST_CHANGE': // does not exist? + case 'PUSH_LIST_ITEM_CHANGE': + /* + { + destinationUserId:'A12XXXXXWISGT', + listId:'YW16bjEuYWNjb3VudC5BRzJGWEpGWE5DRDZNVzNRSUdFM0xLWkZCWFhRLVRBU0s=', + eventName:'itemCreated', + version:1, + listItemId:'c6852978-bb79-44dc-b7e5-8f5e577432cf' + } + */ + this.emit('ws-todo-change', { + destinationUserId: payload.destinationUserId, + eventType: payload.eventName, // itemCreated, itemUpdated (including checked ToDo), itemDeleted + listId: payload.listId, + listItemVersion: payload.version, + listItemId: payload.listItemId + }); + return; + + case 'PUSH_MICROPHONE_STATE': + case 'PUSH_DELETE_DOPPLER_ACTIVITIES': + case 'PUSH_DEVICE_SETUP_STATE_CHANGE': + case 'NotifyNowPlayingUpdated': // TODO + case 'NotifyMediaSessionsUpdated': + return; - this.httpsGet (`/api/todos?deviceSerialNumber=${dev.serialNumber}&deviceType=${dev.deviceType}`, - callback, - { - method: 'POST', - data: JSON.stringify (o), - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' } - // Content-Type: application/x-www-form-urlencoded; - // charset=UTF-8",#\r\n - // Referer: https://alexa.amazon.de/spa/index.html' - } - ); -}; -function _00(val) { - let s = val.toString(); - while (s.length) s = '0'+s; - return s; -} + this.emit('ws-unknown-command', command, payload); + }); -AlexaRemote.prototype.setReminder = function (serialOrName, timestamp, label, callback) { - let dev = this.find(serialOrName, callback); - if (!dev) return; - - let time = new Date(timestamp); - let o = { - type: '"Reminder', - status: "ON", - alarmTime: timestamp, - originalTime: `${_00 (time.getHours ())}:${_00 (time.getMinutes ())}:${_00 (time.getSeconds ())}.000`, - originalDate: `${time.getFullYear ()}-${_00 (time.getMonth () + 1)}.${_00 (time.getDay ())}`, - deviceSerialNumber: dev.serialNumber, - deviceType: dev.deviceType, - reminderLabel: label, - isSaveInFlight: true, - id: 'createReminder', - createdDate: this.now() - }; - this.httpsGet (`/api/notifications/createReminder`, - callback, - { - method: 'PUT', - data: JSON.stringify (o), - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' + this.alexahttp2Push.connect(); + } + + getPushedActivities() { + if (!this._options.autoQueryActivityOnTrigger) return; + this._options.logger && this._options.logger(`Alexa-Remote: Get pushed activities ... ${this.activityUpdateQueue.length} entries in queue (already running: ${this.activityUpdateRunning})`); + if (this.activityUpdateRunning || !this.activityUpdateQueue.length) return; + this.activityUpdateRunning = true; + let earliestActionDate = Date.now(); + this.activityUpdateQueue.forEach(entry => { + if (entry.activityTimestamp < earliestActionDate) earliestActionDate = entry.activityTimestamp; + }); + this.getCustomerHistoryRecords({ + maxRecordSize: this.activityUpdateQueue.length + 2, + filter: false, + forceRequest: true, + startTime: earliestActionDate - 60000, + }, (err, res) => { + this.activityUpdateRunning = false; + if (!res || (err && err.message.includes('no body'))) { + err = null; + res = []; } - // Content-Type: application/x-www-form-urlencoded; - // charset=UTF-8",#\r\n - // Referer: https://alexa.amazon.de/spa/index.html' - } - ); -}; + if (!err) { + res.reverse(); + this._options.logger && this._options.logger(`Alexa-Remote: Activity data ${JSON.stringify(res)}`); // TODO REMOVE + + let lastFoundQueueIndex = -1; + this.activityUpdateQueue.forEach((entry, queueIndex) => { + if (entry.key) { // deprecated + const found = res.findIndex(activity => activity.data.recordKey.endsWith(`#${entry.key.entryId}`) && activity.data.customerId === entry.key.registeredUserId); -AlexaRemote.prototype.getHomeGroup = function (callback) { - this.httpsGet (`https://alexa-comms-mobile-service.amazon.com/users/${this.commsId}/identities?includeUserName=true`, callback); -}; + if (found === -1) { + this._options.logger && this._options.logger(`Alexa-Remote: Activity for id ${entry.key.entryId} not found`); + } else { + lastFoundQueueIndex = queueIndex; + this.activityUpdateQueue.splice(0, lastFoundQueueIndex + 1); + const activity = res.splice(found, 1)[0]; + this._options.logger && this._options.logger(`Alexa-Remote: Activity found entry ${found} for Activity ID ${entry.key.entryId}`); + activity.destinationUserId = entry.destinationUserId; + this.emit('ws-device-activity', activity); + } + } else { + const lastPushedActivity = this.lastPushedActivity[entry.deviceSerialNumber] || Date.now() - 30000; + const found = res.filter(activity => activity.data.recordKey.endsWith(`#${entry.deviceSerialNumber}`) && activity.data.customerId === entry.destinationUserId && activity.creationTimestamp >= entry.activityTimestamp - 10000 && activity.creationTimestamp > lastPushedActivity); // Only if current stuff is found + if (found.length === 0) { + this._options.logger && this._options.logger(`Alexa-Remote: Activity for device ${entry.deviceSerialNumber} not found`); + } else { + let foundSomething = false; + found.forEach((activity, index) => { + if (activity.data.utteranceType === 'WAKE_WORD_ONLY' && index === 0 && this.activityUpdateNotFoundCounter > 0 && found.length > 1) return; + this._options.logger && this._options.logger(`Alexa-Remote: Activity (ts=${activity.creationTimestamp}) found for device ${entry.deviceSerialNumber}`); + activity.destinationUserId = entry.destinationUserId; + this.emit('ws-device-activity', activity); + if (activity.data.utteranceType !== 'WAKE_WORD_ONLY') { + this.lastPushedActivity[entry.deviceSerialNumber] = activity.creationTimestamp; + foundSomething = true; + } else { + this._options.logger && this._options.logger(`Alexa-Remote: Only Wakeword activity for device ${entry.deviceSerialNumber} found. try again in 2,5s`); + if (!foundSomething) { + lastFoundQueueIndex = -2; + } + } + }); + if (foundSomething) { + lastFoundQueueIndex = queueIndex; + this.activityUpdateQueue.splice(queueIndex, 1); + } + } + } + }); -function test () { - AlexaRemote.prototype.getFeatureAlertLocation = function (callback) { - alexa.httpsGet (`https://alexa.amazon.de/api/feature-alert-location?`, callback) - }; + if (lastFoundQueueIndex < 0) { + this._options.logger && this._options.logger(`Alexa-Remote: No activities from stored ${this.activityUpdateQueue.length} entries found in queue (${this.activityUpdateNotFoundCounter})`); + this.activityUpdateNotFoundCounter++; + if ( + (lastFoundQueueIndex === -1 && this.activityUpdateNotFoundCounter > 2) || // 2 tries without wakeword + (lastFoundQueueIndex === -2 && this.activityUpdateNotFoundCounter > 5) // 5 tries with wakeword + ) { + this._options.logger && this._options.logger('Alexa-Remote: Reset expected activities'); + this.activityUpdateQueue = []; + this.activityUpdateNotFoundCounter = 0; + } + } + else { + this.activityUpdateNotFoundCounter = 0; + this._options.logger && this._options.logger(`Alexa-Remote: ${this.activityUpdateQueue.length} entries left in activity queue`); + } + } - let alexa = AlexaRemote ().init (cookie, function () { + if (!err && this.activityUpdateQueue.length) { + this.activityUpdateTimeout = setTimeout(() => { + this.activityUpdateTimeout = null; + this.getPushedActivities(); + }, 4000); + } - alexa.getHomeGroup (function (err, res) { - res = res; - }) - // alexa.getPlayer('wohnzimmer', function(err, res) { - // res = res; - // }) - alexa.httpsGet (`https://alexa.amazon.de/api/devices-v2/device?cached=true&"`, function (err, res) { - res = res; }); + } + stop() { + if (this.cookieRefreshTimeout) { + clearTimeout(this.cookieRefreshTimeout); + this.cookieRefreshTimeout = null; + } + if (this.alexahttp2Push) { + this.alexahttp2Push.disconnect(); + } + } - //alexa.sendCommand('Wohnzimmer', 'volume', 20, function(res) { - //alexa.sendCommand('Küche (Sonos)', 'play', 0, function(res) { - alexa.sendCommand ('wohnzimmer', 'play', 0, function (res) { - res = res; - }); + generateCookie(email, password, callback) { + if (!this.alexaCookie) this.alexaCookie = require('alexa-cookie2'); + this.alexaCookie.generateAlexaCookie(email, password, this._options, callback); + } - // alexa.tuneinSearch('wdr 4', alexa.ownerCustomerId, function(err, res) { - // res = res; - // - // alexa.setTunein ('Schlafzimmer', res.browseList[0].id, alexa.ownerCustomerId, function(err, res) { - // res = res; - // }); - // }) + refreshCookie(callback) { + if (!this.alexaCookie) this.alexaCookie = require('alexa-cookie2'); + this.alexaCookie.refreshAlexaCookie(this._options, callback); + } + getAuthApiBearerToken(callback) { + if (!this.alexaCookie) this.alexaCookie = require('alexa-cookie2'); + const deviceAppName = (this._options.formerRegistrationData && this._options.formerRegistrationData.deviceAppName) || this.alexaCookie.getDeviceAppName(); // Use the App Name from last cookie response or use default one + this.httpsGet(true, `https://api.${this._options.amazonPage}/auth/token`, (err, res) => { + if (err) { + this._options.logger && this._options.logger(`Alexa-Remote: Error getting auth token: ${err.message}`); + callback(err); + } + else { + this._options.logger && this._options.logger(`Alexa-Remote: Auth token: ${res.access_token}`); + callback(null, res); + } + }, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + data: `app_name=${encodeURIComponent(deviceAppName)}&app_version=2.2.556530.0&di.sdk.version=6.12.4&source_token=${encodeURIComponent(this.cookieData.refreshToken)}&package_name=com.amazon.echo&di.hw.version=iPhone&platform=iOS&requested_token_type=access_token&source_token_type=refresh_token&di.os.name=iOS&di.os.version=16.6¤t_version=6.12.4&previous_version=6.12.4` + }); + } - return; - alexa.getHistory ({filter: true}, function (res) { - res = res; + updateApiBearerToken(callback) { + if (this.authApiBearerToken && Date.now() < this.authApiBearerExpiry) { + return callback && callback(null); + } + this.getAuthApiBearerToken((err, res) => { + if (err || !res || !res.access_token || res.token_type !== 'bearer') { + this._options.logger && this._options.logger(`Alexa-Remote: Error getting auth token: ${err ? err.message : res}`); + return callback && callback(err); + } + this.authApiBearerToken = res.access_token; + this.authApiBearerExpiry = Date.now() + res.expires_in * 1000; + callback && callback(err); }); - alexa.tuneinSearch ('wdr 4', alexa.ownerCustomerId, function (err, res) { - res = res; + } - //alexa.setTunein ('Schlafzimmer', res.browseList[0].id, alexa.ownerCustomerId, function(err, res) { - alexa.setTunein ('Wohnzimmer', res.browseList[0].id, alexa.ownerCustomerId, function (err, res) { - res = res; + httpsGetAuthApi(path, callback, flags = {}) { + if (!this.endpoints && !this.endpoints.alexaApiUrl) { + this._options.logger && this._options.logger(`Alexa-Remote: No endpoint set for alexaApiUrl: ${JSON.stringify(this.endpoints)}`); + return callback && callback(new Error(`No endpoint set for alexaApiUrl: ${JSON.stringify(this.endpoints)}`)); + } + flags = flags || {}; + flags.host = this.endpoints.alexaApiUrl.replace(/^https?:\/\//, ''); + flags.headers = flags.headers || {}; + flags.cleanHeader = true; + flags.headers = flags.headers || {}; + flags.headers.authorization = this.authApiBearerToken; + flags.headers.authority = flags.host; + flags.headers['user-agent'] = `${officialUserAgent} ${this._options.apiUserAgentPostfix}`.trim(); + if (!this.authApiBearerToken || Date.now() >= this.authApiBearerExpiry) { + this.updateApiBearerToken(err => { + if (err) { + return callback && callback(err); + } + flags.headers.authorization = this.authApiBearerToken; + this.httpsGet(true, path, callback, flags); }); - }) - // alexa.getDeviceStatusList((ret) => { - // ret = ret; - // }) - // alexa.getDevices((err, res) => { - // res = res; - // }); - // alexa.getNotifications((err, res) => { - // res = res; - // }) - // alexa.getBluetooth((err, res) => { - // res = res; - // }) - }); + } else { + flags = flags || {}; + this.httpsGet(true, path, callback, flags); + } + } -} + httpsGet(noCheck, path, callback, flags = {}) { + if (typeof noCheck !== 'boolean') { + flags = callback; + callback = path; + path = noCheck; + noCheck = false; + } + // bypass check because set or last check done before less than 30 mins + if (noCheck || (new Date().getTime() - this.lastAuthCheck) < 1800000) { + this._options.logger && this._options.logger(`Alexa-Remote: No authentication check needed (time elapsed ${new Date().getTime() - this.lastAuthCheck})`); + return this.httpsGetCall(path, callback, flags); + } + this.checkAuthentication((authenticated, err) => { + if (authenticated) { + this._options.logger && this._options.logger('Alexa-Remote: Authentication check successfull'); + this.lastAuthCheck = new Date().getTime(); + return this.httpsGetCall(path, callback, flags); + } + else if (err && authenticated === null) { + this._options.logger && this._options.logger(`Alexa-Remote: Authentication check returned error: ${err}. Still try request`); + return this.httpsGetCall(path, callback, flags); + } + this._options.logger && this._options.logger('Alexa-Remote: Authentication check Error, try re-init'); + delete this._options.csrf; + delete this._options.cookie; + this.init(this._options, function(err) { + if (err) { + this._options.logger && this._options.logger('Alexa-Remote: Authentication check Error and renew unsuccessful. STOP'); + return callback && callback(new Error('Cookie invalid, Renew unsuccessful')); + } + return this.httpsGet(path, callback, flags); + }); + }); + } -AlexaRemote.prototype.getDevicePreferences = function (callback) { - this.httpsGet ('https://alexa.amazon.de/api/device-preferences?cached=true&_=%t', callback); -}; + // API Host call without Authorization header + httpsApiGet(path, callback, flags) { + if (!this.endpoints || !this.endpoints.alexaApiUrl) { + this._options.logger && this._options.logger(`Alexa-Remote: No endpoint set for alexaApiUrl: ${JSON.stringify(this.endpoints)}`); + return callback && callback(new Error(`No endpoint set for alexaApiUrl: ${JSON.stringify(this.endpoints)}`)); + } + flags = flags || {}; + if (!flags.host) { + flags.host = this.endpoints.alexaApiUrl.replace(/^https?:\/\//, ''); + } + return this.httpsGetCall(path,callback, flags); + } -AlexaRemote.prototype.getSmarthomeDevices = function (callback) { - this.httpsGet ('https://alexa.amazon.de/api/phoenix?_=%t', function (err, res) { - if (err || !res || !res.networkDetail) return callback(err, res); - try { - res = JSON.parse(res.networkDetail); - } catch(e) { - return callback('invalid JSON'); - } - if (!res.locationDetails) return callback('locationDetails not found'); - callback (err, res.locationDetails) - }); -}; - -AlexaRemote.prototype.renameDevice = function (serialOrName, newName, callback) { - let dev = this.find(serialOrName, callback); - if (!dev) return; - let o = { - accountName: newName, - serialNumber: dev.serialNumber, - deviceAccountId: dev.deviceAccountId, - deviceType: dev.deviceType, - //deviceOwnerCustomerId: oo.deviceOwnerCustomerId - }; - this.httpsGet (`https://alexa.amazon.de/api/devices-v2/device/${dev.serialNumber}`, - callback, - { - method: 'PUT', - data: JSON.stringify (o), - } - ); -}; - - -AlexaRemote.prototype.deleteSmarthomeDevice = function (smarthomeDevice, callback) { - let flags = { - method: 'DELETE' - //data: JSON.stringify (o), - }; - this.httpsGet (`https://alexa.amazon.de/api/phoenix/appliance/${smarthomeDevice}`, callback, flags); -}; - -AlexaRemote.prototype.deleteAllSmarthomeDevices = function (callback) { - let flags = { - method: 'DELETE' - //data: JSON.stringify (o), - }; - this.httpsGet (`https://alexa.amazon.de/api/phoenix`, callback, flags); -}; - - - -AlexaRemote.prototype.discoverSmarthomeDevice = function (callback) { - let flags = { - method: 'POST' - //data: JSON.stringify (o), - }; - this.httpsGet ('https://alexa.amazon.de/api/phoenix/discovery', callback, flags); -}; - - -AlexaRemote.prototype.unpaireBluetooth = function (serialOrName, btAddress, callback) { - let dev = this.find(serialOrName, callback); - if (!dev) return; - let flags = { - method: 'POST', - data: JSON.stringify ({ - bluetoothDeviceAddress: btAddress, - bluetoothDeviceClass: "OTHER" - }) - }; - this.httpsGet (`https://alexa.amazon.de/api/bluetooth/unpair-sink/${dev.deviceType}/${dev.serialNumber}`, callback, flags); -}; - -AlexaRemote.prototype.deleteDevice = function (serialOrName, callback) { - let dev = this.find(serialOrName, callback); - if (!dev) return; - let flags = { - method: 'DELETE', - data: JSON.stringify ({ - deviceType: dev.deviceType - }) - }; - this.httpsGet (`https://alexa.amazon.de/api/devices/device/${dev.serialNumber}?deviceType=${dev.deviceType}`, callback, flags); -}; + httpsGetCall(path, callback, flags = {}) { + const handleResponse = (err, res, body) => { + if (!err && typeof res.statusCode === 'number' && res.statusCode === 401) { + this._options.logger && this._options.logger('Alexa-Remote: Response: 401 Unauthorized'); + return callback(new Error('401 Unauthorized'), null); + } -module.exports = AlexaRemote; + if (!err && typeof res.statusCode === 'number' && res.statusCode === 503 && !flags.isRetry) { + this._options.logger && this._options.logger('Alexa-Remote: Response: 503 ... Retrying once'); + flags.isRetry = true; + return setTimeout(() => this.httpsGetCall(path, callback, flags), Math.floor(Math.random() * 500) + 500); + } + + if (err || !body) { // Method 'DELETE' may return HTTP STATUS 200 without body + this._options.logger && this._options.logger(`Alexa-Remote: Response: No body (code=${res && res.statusCode})`); + return typeof res.statusCode === 'number' && res.statusCode >= 200 && res.statusCode < 300 ? callback(null, {'success': true}) : callback(new Error('no body'), null); + } + + if (flags && flags.handleAsText) { + return callback(null, body); + } + let ret; + try { + ret = JSON.parse(body); + } catch (e) { + if (typeof res.statusCode === 'number' && res.statusCode >= 500 && res.statusCode < 510) { + this._options.logger && this._options.logger(`Alexa-Remote: Response: Status: ${res.statusCode}`); + callback(new Error('no body'), null); + callback = null; + return; + } + + this._options.logger && this._options.logger(`Alexa-Remote: Response: No/Invalid JSON : ${body}`); + if ((body.includes('ThrottlingException') || body.includes('Rate exceeded') || body.includes('Too many requests')) && !flags.isRetry) { + let delay = Math.floor(Math.random() * 3000) + 10000; + if (body.includes('Too many requests')) { + delay += 20000 + Math.floor(Math.random() * 30000); + } + this._options.logger && this._options.logger(`Alexa-Remote: rate exceeded response ... Retrying once in ${delay}ms`); + flags.isRetry = true; + return setTimeout(() => this.httpsGetCall(path, callback, flags), delay); + } + callback && callback(new Error('no JSON'), body); + callback = null; + return; + } + + // TODO maybe handle the case of "non HTTP 200 responses" better and return accordingly? + // maybe set err AND body? + // add x-amzn-ErrorType header to err? (e.g. 400 on /player: ExpiredPlayQueueException:http://internal.amazon.com/coral/com.amazon.dee.web.coral.model/) + this._options.logger && this._options.logger(`Alexa-Remote: Response: ${JSON.stringify(ret)}`); + if ((body.includes('ThrottlingException') || body.includes('Rate exceeded') || body.includes('Too many requests')) && !flags.isRetry) { + let delay = Math.floor(Math.random() * 3000) + 10000; + if (body.includes('Too many requests')) { + delay += 20000 + Math.floor(Math.random() * 30000); + } + this._options.logger && this._options.logger(`Alexa-Remote: rate exceeded response ... Retrying once in ${delay}ms`); + flags.isRetry = true; + return setTimeout(() => this.httpsGetCall(path, callback, flags), delay); + } + callback(null, ret); + callback = null; + }; + + flags = flags || {}; + + const options = { + host: flags.host || this.baseUrl, + path: '', + method: 'GET', + timeout: flags.timeout || 10000, + headers: { + 'User-Agent' : `${officialUserAgent} ${this._options.apiUserAgentPostfix}`.trim(), + 'Content-Type': 'application/json; charset=utf-8', + 'Accept': 'application/json; charset=utf-8', + 'Accept-Language': this._options.acceptLanguage || 'de-DE', + 'Referer': `https://alexa.${this._options.amazonPage}/spa/index.html`, + 'Origin': `https://alexa.${this._options.amazonPage}`, + 'csrf' : this.csrf, + 'Cookie' : this.cookie, + 'Accept-Encoding': 'gzip, deflate' + } + }; + + if (flags.cleanHeader) { + delete options.headers.Referer; + delete options.headers.Origin; + delete options.headers.Cookie; + delete options.headers.csrf; + delete options.headers['User-Agent']; + } + path = path.replace(/[\n ]/g, ''); + if (!path.startsWith('/')) { + path = path.replace(/^https:\/\//, ''); + //let ar = path.match(/^([^\/]+)(\/.*$)/); + const ar = path.match(/^([^/]+)(\/*.*$)/); + options.host = ar[1]; + path = ar[2]; + } + const time = new Date().getTime(); + path = path.replace(/%t/g, time); + + options.path = path; + options.method = flags.method ? flags.method : flags.data ? 'POST' : 'GET'; + + if ((options.method === 'GET' || options.method === 'DELETE') && !flags.data) { + delete options.headers['Content-Type']; + } + + if (flags.headers) Object.keys(flags.headers).forEach(n => { + options.headers [n] = flags.headers[n]; + }); + + const logOptions = JSON.parse(JSON.stringify(options)); + delete logOptions.headers.Cookie; + delete logOptions.headers.csrf; + delete logOptions.headers['Accept-Encoding']; + delete logOptions.headers['User-Agent']; + delete logOptions.headers['Content-Type']; + delete logOptions.headers.Accept; + delete logOptions.headers.Referer; + delete logOptions.headers.Origin; + delete logOptions.headers.authorization; + delete logOptions.headers.authority; + delete logOptions.headers['user-agent']; + this._options.logger && this._options.logger(`Alexa-Remote: Sending Request with ${JSON.stringify(logOptions)} ${options.authorization ? '+AccessToken' : ''}${(options.method === 'POST' || options.method === 'PUT' || options.method === 'DELETE') ? ` and data=${flags.data}` : ''}`); + + let req; + let responseReceived = false; + try { + req = https.request(options, (res) => { + const chunks = []; + + res.on('data', (chunk) => { + chunks.push(chunk); + }); + + res.on('end', () => { + responseReceived = true; + if (typeof callback === 'function') { + const resBuffer = Buffer.concat(chunks); + const encoding = res.headers['content-encoding']; + if (encoding === 'gzip') { + zlib.gunzip(resBuffer, (err, decoded) => { + if (typeof callback === 'function') { + handleResponse(err, res, decoded && decoded.toString()); + } else { + this._options.logger && this._options.logger(`Alexa-Remote: Response Status ${res.statusCode}: ${decoded && decoded.toString()}`); + } + }); + } else if (encoding === 'deflate') { + zlib.inflate(resBuffer, (err, decoded) => { + if (typeof callback === 'function') { + handleResponse(err, res, decoded && decoded.toString()); + } else { + this._options.logger && this._options.logger(`Alexa-Remote: Response Status ${res.statusCode}: ${decoded && decoded.toString()}`); + } + }); + } else { + if (typeof callback === 'function') { + handleResponse(null, res, resBuffer.toString()); + } else { + this._options.logger && this._options.logger(`Alexa-Remote: Response Status ${res.statusCode}: ${resBuffer.toString()}`); + } + } + } + }); + }); + } catch(err) { + this._options.logger && this._options.logger(`Alexa-Remote: Response: Exception: ${err}`); + if (typeof callback === 'function'/* && callback.length >= 2*/) { + callback (err, null); + callback = null; + } + return; + } + + req.on('error', (e) => { + this._options.logger && this._options.logger(`Alexa-Remote: Response: Error: ${e}`); + if (!responseReceived && typeof callback === 'function'/* && callback.length >= 2*/) { + callback (e, null); + callback = null; + } + }); + + req.on('timeout', () => { + if (!responseReceived && typeof callback === 'function'/* && callback.length >= 2*/) { + this._options.logger && this._options.logger('Alexa-Remote: Response: Timeout'); + callback (new Error('Timeout'), null); + callback = null; + } + }); + + req.on('close', () => { + if (!responseReceived && typeof callback === 'function'/* && callback.length >= 2*/) { + this._options.logger && this._options.logger('Alexa-Remote: Response: Closed'); + callback (new Error('Connection Closed'), null); + callback = null; + } + }); + + if (flags && flags.data) { + req.write(flags.data); + } + + req.end(); + } + + /// Public + checkAuthentication(callback) { + // If we don't have a cookie assigned, we can't be authenticated + if (this.cookie && this.csrf) { + this.httpsGetCall('/api/customer-status', (err, res) => { + if (!err && res) { + //this.ownerCustomerId = res.authentication.customerId; + return callback(true, null); + } + if (err && !err.message.includes('401')) { + return callback(null, err); + } + callback(false, err); + }); + } else { + callback(false, null); + } + } + + getEndpoints(callback) { + this.httpsGetCall('/api/endpoints', callback); + } + + getDevices(callback) { + this.httpsGet('/api/devices-v2/device?cached=true&_=%t', callback); + } + + getCards(limit, beforeCreationTime, callback) { + if (typeof limit === 'function') { + callback = limit; + limit = 10; + } + if (typeof beforeCreationTime === 'function') { + callback = beforeCreationTime; + beforeCreationTime = '%t'; + } + if (beforeCreationTime === undefined) beforeCreationTime = '%t'; + this.httpsGet(`/api/cards?limit=${limit}&beforeCreationTime=${beforeCreationTime}000&_=%t`, callback); + } + + getUsersMe(callback) { + this.httpsGetCall('/api/users/me?platform=ios&version=2.2.651540.0', callback); + } + + getHousehold(callback) { + this.httpsGet('/api/household', callback); + } + + getMedia(serialOrName, callback) { + const dev = this.find(serialOrName); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + this.httpsGet(`/api/media/state?deviceSerialNumber=${dev.serialNumber}&deviceType=${dev.deviceType}&screenWidth=1392&_=%t`, callback); + } + + getPlayerInfo(serialOrName, callback) { + const dev = this.find(serialOrName); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + this.httpsGet(`/api/np/player?deviceSerialNumber=${dev.serialNumber}&deviceType=${dev.deviceType}&screenWidth=1392&_=%t`, callback); + } + + /** @deprecated Use getListsV2 instead */ + getLists(callback) { + this.getListsV2((err, lists) => { + if (err) { + this._options.logger && this._options.logger(`Alexa-Remote: Error getting lists: ${err.message}`); + return callback && callback(err); + } + const mappedList = lists.map(list => { + let count = null; + try { + count = JSON.parse(list.totalActiveItemsCount).count; + } catch (e) { + // Ignore + } + return { + id: Array.isArray(list.id) && list.id.length > 0 ? list.id[0] : list.listId, + listId: list.listId, + itemId: list.listId, + name: list.listName || list.listType, + createdDateTime: list.createAt, + updatedDateTime: list.updateAt, + totalActiveItemsCount: count, + ownerCustomerId: list.customerId, + }; + }); + callback && callback(null, mappedList); + }); + } + + getListsV2(callback) { + // request options + const request = { + 'method': 'POST', + 'data': JSON.stringify({ + listAttributesToAggregate:[ + /*{ type:'listCategories' }, + { type:'categoryOverride' }, + { type:'shareableInfo','marketplaceId':'A1PA6795UKMFR9','clientId':'AlexaApp' }, + { type:'sharedListOwnerInfo' }, + { type:'collaboratorInfo' },*/ + { type:'totalActiveItemsCount' } + ], + listOwnershipType: null + }) + }; + + this.httpsGet(`https://www.${this._options.amazonPage}/alexashoppinglists/api/v2/lists/fetch`, (err, res) => callback && callback(err, res && res.listInfoList), request); + } + + /** @deprecated Use getListV2 instead */ + getList(listId, callback) { + this.getListV2(listId, (err, list) => { + if (err) { + this._options.logger && this._options.logger(`Alexa-Remote: Error getting lists: ${err.message}`); + return callback && callback(err); + } + let count = null; + try { + count = JSON.parse(list.totalActiveItemsCount).count; + } catch (e) { + // Ignore + } + const mappedList = { + id: Array.isArray(list.id) && list.id.length > 0 ? list.id[0] : list.listId, + listId: list.listId, + itemId: list.listId, + name: list.listName || list.listType, + createdDateTime: list.createAt, + updatedDateTime: list.updateAt, + totalActiveItemsCount: count, + ownerCustomerId: list.customerId, + }; + callback && callback(null, mappedList); + }); + } + + getListV2(listId, callback, limit) { + // request options + const request = { + 'method': 'POST', + 'data': JSON.stringify({ + listAttributesToAggregate: [ + /*{'type':'listCategories'}, + {'type':'shareableInfo','marketplaceId':'A1PA6795UKMFR9','clientId':'AlexaApp'}, + {'type':'keywordRecommendations','clientId':'AlexaApp','marketplaceId':'A1PA6795UKMFR9','locale':'de_DE'},*/ + {'type':'totalActiveItemsCount'}, + /*{'type':'categoryOverride'}*/ + ] + }) + }; + + this.httpsGet(`https://www.${this._options.amazonPage}/alexashoppinglists/api/v2/lists/${listId}/items/fetch?limit=${limit || 100}`, (err, res) => callback && callback(err, res && res.listInfo), request); + } + + /** + * Get items from a list. + * + * @param {String} listId List ID to retrieve items from + * @param {Object} options Options to pass to the request + * @param {number} options.limit Limit of items to retrieve + * @param {function} callback + * + * @deprecated Use getListItemsV2 instead + */ + getListItems(listId, options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + + this.getListItemsV2(listId, options, (err, items) => { + if (err) { + this._options.logger && this._options.logger(`Alexa-Remote: Error getting list: ${err.message}`); + return callback && callback(err); + } + const mappedItems = items.map(item => ({ + id: item.itemId, + listId: item.listId, + value: item.itemName, + customerId: item.customerId, + completed: item.itemStatus === 'COMPLETE', + createdDateTime: item.createAt, + updatedDateTime: item.updateAt, + version: item.version, + note: item.note, + })); + callback && callback(null, mappedItems); + }); + } + + /** + * Get items from a list with v2 API. + * + * @param {String} listId List ID to retrieve items from + * @param {Object} options Options to pass to the request + * @param {number} options.limit Limit of items to retrieve + * @param {function} callback + * + */ + getListItemsV2(listId, options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + + if (!listId) { + this._options.logger && this._options.logger('Alexa-Remote: Error getting list items: No listId provided'); + return callback && callback(new Error('No listId provided')); + } + + const limit = options.limit || 100; + + const request = { + 'method': 'POST', + 'data': JSON.stringify({ + itemAttributesToProject:['quantity',/*'speakerId','asinInfo',*/'note',/*'dealInfo'*/], + /*itemAttributesToAggregate:[ + {type:'categorization'},{type:'speakerInfo','marketplaceId':'A1PA6795UKMFR9'} + ],*/ + /*listAttributesToAggregate:[ + {type:'listCategories'}, + {type:'shareableInfo','marketplaceId':'A1PA6795UKMFR9','clientId':'AlexaApp'}, + {type:'keywordRecommendations','clientId':'AlexaApp','marketplaceId':'A1PA6795UKMFR9','locale':'de_DE'}, + {type:'totalActiveItemsCount'},{type:'categoryOverride'} + ]*/ + }) + }; + + // send request + this.httpsGet(`https://www.${this._options.amazonPage}/alexashoppinglists/api/v2/lists/${listId}/items/fetch?limit=${limit}`, (err, res) => callback && callback(err, res && res.itemInfoList), request); + } + + addListItem(listId, options, callback) { + // get function params + if (typeof options === 'string') { + options = { value: options }; + } + + // request options + const request = { + 'method': 'POST', + 'data': JSON.stringify({ + items : [{ + itemType:'KEYWORD', + itemName: options.value, + /*itemAttributesToCreate: [{ type : 'speakerId'}], + itemAttributesToAggregate : [ + { type:'categorization'}, + { type:'speakerInfo', marketplaceId: 'A1PA6795UKMFR9'} + ]*/ + }] + }) + }; + + // send request + this.httpsGet(`https://www.${this._options.amazonPage}/alexashoppinglists/api/v2/lists/${listId}/items`, (err, res) => callback && callback(err, res && res.itemInfoList), request); + } + + updateListItem(listId, listItemId, options, callback) { + // providing a version is mandatory + if (typeof options !== 'object' || !options.version || !options.value) { + const errors = []; + + if (!options.version && callback) { + errors.push('Providing the current version via options is mandatory!'); + } + + if (!options.value && callback) { + errors.push('Providing a new value (description) via options is mandatory!'); + } + + callback && callback(errors); + return false; + } + + const itemAttributesToUpdate = []; + const itemAttributesToRemove = []; + for (const key in options) { + switch (key) { + case 'value': + itemAttributesToUpdate.push({ type: 'itemName', value: options.value }); + break; + case 'note': + if (options.note === null || options.note === undefined) { + //itemAttributesToRemove.push({ type: 'note' }); + break; + } + itemAttributesToUpdate.push({ type: 'note', value: options.note }); + break; + case 'completed': + itemAttributesToUpdate.push({ type: 'itemStatus', value: options.completed ? 'COMPLETE' : 'ACTIVE' }); + break; + case 'itemStatus': + if (options.completed !== undefined) { + break; // ignore, because itemStatus is handled by completed + } + itemAttributesToUpdate.push({ type: 'itemStatus', value: options.itemStatus }); + break; + case 'quantity': + if (options.quantity === null || options.quantity === undefined) { + //itemAttributesToRemove.push({ type: 'quantity' }); + break; + } + itemAttributesToUpdate.push({ type: 'quantity', value: options.quantity }); + break; + } + } + + // request options + const request = { + 'method': 'PUT', + 'data': JSON.stringify({ + itemAttributesToUpdate, + itemAttributesToRemove, + }) + }; + + // send request + this.httpsGet(`https://www.${this._options.amazonPage}/alexashoppinglists/api/v2/lists/${listId}/items/${listItemId}?version =${options.version}`, (err, res) => callback && callback(err, res && res.itemInfoList), request); + } + + deleteListItem(listId, listItemId, options, callback) { + // providing a version is mandatory + if (typeof options !== 'object' || !options.version) { + const errors = []; + + if (!options.version && callback) { + errors.push('Providing the current version via options is mandatory!'); + } + + callback && callback(errors); + return false; + } + + // request options + const request = { + 'method': 'DELETE', + }; + + // send request + this.httpsGet(`https://www.${this._options.amazonPage}/alexashoppinglists/api/v2/lists/${listId}/items/${listItemId}?version=${options.version}`, callback, request); + } + + getWakeWords(callback) { + this.httpsGet(`/api/wake-word?_=%t`, callback); + } + + getReminders(cached, callback) { + return this.getNotifications(cached, callback); + } + getNotifications(cached, callback) { + if (typeof cached === 'function') { + callback = cached; + cached = true; + } + if (cached === undefined) cached = true; + this.httpsGet(`/api/notifications?cached=${cached}&_=%t`, callback); + } + + getNotificationSounds(serialOrName, alertType, callback) { + if (typeof alertType === 'function') { + callback = alertType; + alertType = ''; + } + const device = this.find(serialOrName); + if (!device) return callback && callback(new Error('Unknown Device or Serial number'), null); + + this.httpsGet(`/api/notification/sounds?deviceType=${device.deviceType}&deviceSerialNumber=${device.serialNumber}&softwareVersion=${device.softwareVersion}${alertType ? `&alertType=${alertType}` : ''}`, callback); + } + + // alarm volume + getDeviceNotificationState(serialOrName, callback) { + const device = this.find(serialOrName); + if (!device) return callback && callback(new Error('Unknown Device or Serial number'), null); + + this.httpsGet(`/api/device-notification-state/${device.deviceType}/${device.softwareVersion}/${device.serialNumber}`, callback); + } + + setDeviceNotificationVolume(serialOrName, volumeLevel, callback) { + const device = this.find(serialOrName); + if (!device) return callback && callback(new Error('Unknown Device or Serial number'), null); + + this.httpsGet(`/api/device-notification-state/${device.deviceType}/${device.softwareVersion}/${device.serialNumber}`, callback , { + 'method': 'PUT', + 'data': JSON.stringify({ + volumeLevel + }) + }); + } + + setDeviceNotificationDefaultSound(serialOrName, notificationType, soundId, callback) { + const device = this.find(serialOrName); + if (!device) return callback && callback(new Error('Unknown Device or Serial number'), null); + + this.httpsGet(`/api/notification/default-sound`, callback, { + 'method': 'PUT', + 'data': JSON.stringify({ + 'deviceType': device.deviceType, + 'deviceSerialNumber': device.serialNumber, + notificationType, + soundId + }) + }); + } + + getDeviceNotificationDefaultSound(serialOrName, notificationType, callback) { + const device = this.find(serialOrName); + if (!device) return callback && callback(new Error('Unknown Device or Serial number'), null); + + this.httpsGet(`/api/notification/default-sound?deviceType=${device.deviceType}&deviceSerialNumber=${device.serialNumber}¬ificationType=${notificationType}`, callback); + } + + getAscendingAlarmState(serialOrName, callback) { + if (typeof serialOrName === 'function') { + callback = serialOrName; + serialOrName = undefined; + } + + this.httpsGet(`/api/ascending-alarm`, (err, res) => { + if (serialOrName) { + const device = this.find(serialOrName); + if (!device) return callback && callback(new Error('Unknown Device or Serial number'), null); + callback && callback(err, res && res.ascendingAlarmModelList && res.ascendingAlarmModelList.find(d => d.deviceSerialNumber === device.serialNumber)); + } else { + callback && callback(err, res ? res.ascendingAlarmModelList : res); + } + }); + } + + setDeviceAscendingAlarmState(serialOrName, ascendingAlarmEnabled, callback) { + const device = this.find(serialOrName); + if (!device) return callback && callback(new Error('Unknown Device or Serial number'), null); + + this.httpsGet(`/api/ascending-alarm/${device.serialNumber}`, callback, { + 'method': 'PUT', + 'data': JSON.stringify({ + deviceSerialNumber: device.serialNumber, + deviceType: device.deviceType, + ascendingAlarmEnabled + }) + }); + } + + getSkills(callback) { + // request options + const request = { + 'method': 'GET', + 'headers': { + 'Accept': 'application/vnd+amazon.uitoolkit+json;ns=1;fl=0' + } + }; + + // send request + this.httpsGet(`https://skills-store.${this._options.amazonPage}/app/secure/your-skills-page?deviceType=app&ref-suffix=ysa_gw&pfm=A1PA6795UKMFR9&cor=DE&lang=en-us&_=%t`, function (err, res) { + const data = res.find(o => o.block === 'data' && Array.isArray(o.contents)) + .contents + .find(o => o.id === 'skillsPageData') + .contents + .products + .map(o => ({ + id: o.productMetadata.skillId, + name: o.title, + type: o.productDetails.skillTypes[0] + })); + + callback && callback(err, data); + + }, request); + } + + getWholeHomeAudioGroups(callback) { + this.httpsGet('/api/wholeHomeAudio/v1/groups', (err, res) => callback && callback(err, res && res.groups)); + } + + createNotificationObject(serialOrName, type, label, value, status, sound, recurring) { // type = Reminder, Alarm + if (status && typeof status === 'object') { + sound = status; + status = 'ON'; + } + if (value === null || value === undefined) { + value = new Date().getTime() + 5000; + } + + const dev = this.find(serialOrName); + if (!dev) return null; + + const now = new Date(); + let notification = { + 'alarmTime': now.getTime(), // will be overwritten + 'createdDate': now.getTime(), + 'type': type, // Alarm ... + 'deviceSerialNumber': dev.serialNumber, + 'deviceType': dev.deviceType, + 'reminderLabel': type !== 'Timer' ? (label || null) : null, + 'timerLabel': type === 'Timer' ? (label || null) : null, + 'sound': (sound && typeof sound === 'object') ? sound: null, + /*{ + 'displayName': 'Countertop', + 'folder': null, + 'id': 'system_alerts_repetitive_04', + 'providerId': 'ECHO', + 'sampleUrl': 'https://s3.amazonaws.com/deeappservice.prod.notificationtones/system_alerts_repetitive_04.mp3' + }*/ + 'originalDate': `${now.getFullYear()}-${_00(now.getMonth() + 1)}-${_00(now.getDate())}`, + 'originalTime': `${_00(now.getHours())}:${_00(now.getMinutes())}:${_00(now.getSeconds())}.000`, + 'id': null, + + 'isRecurring' : !!recurring, + 'recurringPattern': null, + + 'timeZoneId': null, + 'reminderIndex': null, + + 'isSaveInFlight': true, + + 'status': status ? 'ON' : 'OFF', + }; + if (recurring) { + notification.rRuleData = { + byWeekDays: recurring.byDay, + intervals: [recurring.interval], + frequency: recurring.freq, + flexibleRecurringPatternType: 'EVERY_X_WEEKS', + notificationTimes: [notification.originalTime], + recurStartDate: notification.originalDate, + recurStartTime: '00:00:00.000' + }; + } + + notification = this.parseValue4Notification(notification, value); + + // New style we need to add device! + if (notification.trigger && notification.extensions) { + notification.endpointId = `${dev.serialNumber}@${dev.deviceType}`; + if (recurring) { + notification.trigger.recurrence = recurring; + } + } + + return notification; + } + + //TODO: Hack for now, make better and clean + convertNotificationToV2(notification) { + return this.parseValue4Notification(notification, null); + } + + parseValue4Notification(notification, value) { + let dateOrTimeAdjusted = false; + switch (typeof value) { + case 'object': + if (value instanceof Date) { + if (notification.type !== 'Timer') { + notification.alarmTime = value.getTime(); + notification.originalDate = `${value.getFullYear()}-${_00(value.getMonth() + 1)}-${_00(value.getDate())}`; + notification.originalTime = `${_00(value.getHours ())}:${_00(value.getMinutes ())}:${_00(value.getSeconds ())}.000`; + dateOrTimeAdjusted = true; + } + } else { + notification = extend(notification, value); // we combine the objects + /* + { + 'alarmTime': 0, + 'createdDate': 1522585752734, + 'deferredAtTime': null, + 'deviceSerialNumber': 'G090LF09643202VS', + 'deviceType': 'A3S5BH2HU6VAYF', + 'geoLocationTriggerData': null, + 'id': 'A3S5BH2HU6VAYF-G090LF09643202VS-17ef9b04-cb1d-31ed-ab2c-245705d904be', + 'musicAlarmId': null, + 'musicEntity': null, + 'notificationIndex': '17ef9b04-cb1d-31ed-ab2c-245705d904be', + 'originalDate': '2018-04-01', + 'originalTime': '20:00:00.000', + 'provider': null, + 'recurringPattern': null, + 'remainingTime': 0, + 'reminderLabel': null, + 'sound': { + 'displayName': 'Countertop', + 'folder': null, + 'id': 'system_alerts_repetitive_04', + 'providerId': 'ECHO', + 'sampleUrl': 'https://s3.amazonaws.com/deeappservice.prod.notificationtones/system_alerts_repetitive_04.mp3' + }, + 'status': 'OFF', + 'timeZoneId': null, + 'timerLabel': null, + 'triggerTime': 0, + 'type': 'Alarm', + 'version': '4' + } + */ + } + break; + case 'number': + if (notification.type !== 'Timer') { + value = new Date(value); + notification.alarmTime = value.getTime(); + notification.originalDate = `${value.getFullYear()}-${_00(value.getMonth() + 1)}-${_00(value.getDate())}`; + notification.originalTime = `${_00 (value.getHours ())}:${_00 (value.getMinutes ())}:${_00 (value.getSeconds ())}.000`; + dateOrTimeAdjusted = true; + } + break; + case 'boolean': + notification.status = value ? 'ON' : 'OFF'; + break; + case 'string': { + if (notification.type !== 'Timer') { + const date = new Date(notification.alarmTime); + if (value.match(/^\d{4}-\d{1,2}-\d{1,2}$/)) { + // Does not work that way!! + const ar = value.split('-'); + date.setFullYear(ar[0]); + date.setMonth(ar[1] - 1); + date.setDate(ar[2]); + notification.originalDate = `${date.getFullYear()}-${_00(date.getMonth() + 1)}-${_00(date.getDate())}`; + dateOrTimeAdjusted = true; + } else if (value.match(/^\d{1,2}:\d{1,2}:?\d{0,2}$/)) { + const ar = value.split(':'); + date.setHours(parseInt(ar[0], 10), ar.length > 1 ? parseInt(ar[1], 10) : 0, ar.length > 2 ? parseInt(ar[2], 10) : 0); + notification.originalTime = `${_00(date.getHours())}:${_00(date.getMinutes())}:${_00(date.getSeconds())}.000`; + dateOrTimeAdjusted = true; + } + } + break; + } + } + + const originalDateTime = `${notification.originalDate} ${notification.originalTime}`; + const bits = originalDateTime.split(/\D/); + const date = new Date(bits[0], --bits[1], bits[2], bits[3], bits[4], bits[5]); + if (date.getTime() < Date.now()) { + date.setDate(date.getDate() + 1); + notification.originalDate = `${date.getFullYear()}-${_00(date.getMonth() + 1)}-${_00(date.getDate())}`; + notification.originalTime = `${_00(date.getHours())}:${_00(date.getMinutes())}:${_00(date.getSeconds())}.000`; + } + + if ((typeof value === 'boolean' || value === null || dateOrTimeAdjusted) && (notification.type === 'Alarm' || notification.type === 'MusicAlarm')) { + const newPutNotification = { + trigger: { + scheduledTime: `${notification.originalDate}T${notification.originalTime.substring(0, notification.originalTime.length - 4)}` + }, + extensions: [] + }; + if (notification.sound && notification.sound.id && !notification.musicAlarmId) { + newPutNotification.assets = [{ + type: 'TONE', + assetId: notification.sound.id + }]; + } else if (notification.musicAlarmId) { + newPutNotification.assets = [{ + type: 'TONE', + assetId: `MUSIC-${notification.musicAlarmId}` + }]; + } + return newPutNotification; + } + + return notification; + } + + createNotification(notification, callback) { + // Alarm new style + if (notification.trigger && notification.extensions) { + const flags = { + method: 'POST', + data: JSON.stringify(notification) + }; + return this.httpsGetAuthApi(`/v1/alerts/alarms`, callback, flags); + } + + const flags = { + data: JSON.stringify(notification), + method: 'PUT' + }; + this.httpsGet(`/api/notifications/null`, callback, flags); + } + + changeNotification(notification, value, callback) { + const finalNotification = this.parseValue4Notification(notification, value); + + if (finalNotification.trigger && finalNotification.assets && finalNotification.extensions) { + if (typeof value === 'boolean') { // Switch on/off + if (value) { + return this.activateNotificationV2(notification.notificationIndex, finalNotification, callback); + } else { + return this.deactivateNotificationV2(notification.notificationIndex, callback); + } + } + + return this.setNotificationV2(notification.notificationIndex, finalNotification, callback); + } + + return this.setNotification(finalNotification, callback); + } + + setNotification(notification, callback) { + if (notification.type === 'Reminder') { + delete notification.alarmTime; + delete notification.originalTime; + delete notification.originalDate; + } + if (notification.rRuleData) { + delete notification.rRuleData.recurrenceRules; + if ((notification.rRuleData.notificationTimes && notification.rRuleData.notificationTimes.length) || notification.recurringPattern === 'P1D') { + notification.rRuleData.frequency = 'DAILY'; + } else { + notification.rRuleData.frequency = 'WEEKLY'; + } + } + + const flags = { + method: 'PUT', + data: JSON.stringify(notification) + }; + this.httpsGet(`/api/notifications/${notification.id}`, function(err, res) { + // {'Message':null} + callback && callback(err, res); + }, + flags + ); + } + + setNotificationV2(notificationIndex, notification, callback) { + const flags = { + method: 'PUT', + data: JSON.stringify(notification) + }; + this.httpsGetAuthApi (`/v1/alerts/alarms/${notificationIndex}`, callback, flags); + } + + activateNotificationV2(notificationIndex, notification, callback) { + const flags = { + method: 'PUT', + data: JSON.stringify(notification) + }; + this.httpsGetAuthApi (`/v1/alerts/alarms/${notificationIndex}/activate`, callback, flags); + } + + deactivateNotificationV2(notificationIndex, callback) { + const flags = { + method: 'PUT', + data: '' + }; + this.httpsGetAuthApi (`/v1/alerts/alarms/${notificationIndex}/cancel`, callback, flags); + } + + deleteNotification(notification, callback) { + const flags = { + data: JSON.stringify (notification), + method: 'DELETE' + }; + this.httpsGet(`/api/notifications/${notification.id}`, function(err, res) { + // {'Message':null} + callback && callback(err, res); + }, + flags + ); + } + + cancelNotification(notification, callback) { + if (notification.type === 'Alarm' || notification.type === 'MusicAlarm') { + const flags = { + method: 'PUT' + }; + + this.httpsGetAuthApi(`/v1/alerts/alarms/${notification.notificationIndex}/nextOccurrence/cancel`, callback, flags); + } else if (notification.type === 'Reminder') { + notification.status = 'INSTANCE_CANCELED'; + + const flags = { + data: JSON.stringify (notification), + method: 'PUT' + }; + this.httpsGet(`/api/notifications/${notification.id}`, callback, flags); + } + } + + getDoNotDisturb(callback) { + return this.getDeviceStatusList(callback); + } + getDeviceStatusList(callback) { + this.httpsGet(`/api/dnd/device-status-list?_=%t`, callback); + } + + getBluetooth(cached, callback) { + if (typeof cached === 'function') { + callback = cached; + cached = true; + } + if (cached === undefined) cached = true; + this.httpsGet(`/api/bluetooth?cached=${cached}`, callback); + } + + tuneinSearchRaw(query, callback) { + if (!this.ownerCustomerId) { + this.getUsersMe((err, res) => { + if (err || !res || !res.id) return callback && callback(err, null); + + this.ownerCustomerId = res.id; + this.tuneinSearchRaw(query, callback); + }); + } else { + this.httpsGet(`/api/tunein/search?query=${query}&mediaOwnerCustomerId=${this.ownerCustomerId}&_=%t`, callback); + } + } + + tuneinSearch(query, callback) { + query = querystring.escape(query); + this.tuneinSearchRaw(query, callback); + } + + setTunein(serialOrName, guideId, contentType, callback) { + if (typeof contentType === 'function') { + callback = contentType; + contentType = 'station'; + } + const dev = this.find(serialOrName); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + const encodedStationId = `["music/tuneIn/${contentType}Id","${guideId}"]|{"previousPageId":"TuneIn_SEARCH"}`; + const encoding1 = Buffer.from(encodedStationId).toString('base64'); + const encoding2 = Buffer.from(encoding1).toString('base64'); + const tuneInJson = { + contentToken: `music:${encoding2}` + }; + const flags = { + data: JSON.stringify(tuneInJson), + method: 'PUT' + }; + this.httpsGet(`/api/entertainment/v1/player/queue?deviceSerialNumber=${dev.serialNumber}&deviceType=${dev.deviceType}`, callback, flags); + + /* + this.httpsGet(`/api/tunein/queue-and-play + ?deviceSerialNumber=${dev.serialNumber} + &deviceType=${dev.deviceType} + &guideId=${guideId} + &contentType=${contentType} + &callSign= + &mediaOwnerCustomerId=${dev.deviceOwnerCustomerId}`, callback, { method: 'POST' }); + */ + } + + _getCustomerHistoryRecords(options, callback) { + let url = `https://www.${this._options.amazonPage}/alexa-privacy/apd/rvh/customer-history-records-v2` + + `?startTime=${options.startTime || (Date.now() - 24 * 60 * 60 * 1000)}` + + `&endTime=${options.endTime || Date.now() + 24 * 60 * 60 * 1000}`; + if (options.recordType && options.recordType !== 'VOICE_HISTORY') { + url += `&pageType=${options.recordType}`; + } + //ignoring maxSize for now and just take the 10 that are returned. `&maxRecordSize=${options.maxRecordSize || 1}`, + this.httpsGet( + url, + (err, result) => { + if (err || !result) return callback/*.length >= 2*/ && callback(err, result); + + const ret = []; + if (result.customerHistoryRecords) { + for (let r = 0; r < result.customerHistoryRecords.length; r++) { + const res = result.customerHistoryRecords[r]; + const o = { + data: res + }; + const convParts = {}; + if (res.voiceHistoryRecordItems && Array.isArray(res.voiceHistoryRecordItems)) { + res.voiceHistoryRecordItems.forEach(item => { + convParts[item.recordItemType] = convParts[item.recordItemType] || []; + convParts[item.recordItemType].push(item); + }); + o.conversionDetails = convParts; + } + + const recordKey = res.recordKey.split('#'); // A3NSX4MMJVG96V#1612297041815#A1RABVCI4QCIKC#G0911W0793360TLG + + o.deviceType = recordKey[2] || null; + //o.deviceAccountId = res.sourceDeviceIds[i].deviceAccountId || null; + o.creationTimestamp = res.timestamp || null; + //o.activityStatus = res.activityStatus || null; // DISCARDED_NON_DEVICE_DIRECTED_INTENT, SUCCESS, FAIL, SYSTEM_ABANDONED + + o.deviceSerialNumber = recordKey[3]; + if (!this.serialNumbers[o.deviceSerialNumber]) continue; + o.name = this.serialNumbers[o.deviceSerialNumber].accountName; + const dev = this.find(o.deviceSerialNumber); + const wakeWord = (dev && dev.wakeWord) ? dev.wakeWord : null; + + o.description = {'summary': ''}; + if (convParts.CUSTOMER_TRANSCRIPT || convParts.ASR_REPLACEMENT_TEXT) { + if (convParts.CUSTOMER_TRANSCRIPT) { + convParts.CUSTOMER_TRANSCRIPT.forEach(trans => { + let text = trans.transcriptText; + if (wakeWord && text.startsWith(wakeWord)) { + text = text.substr(wakeWord.length).trim(); + } else if (text.startsWith('alexa')) { + text = text.substr(5).trim(); + } + o.description.summary += `${text}, `; + }); + } + if (convParts.ASR_REPLACEMENT_TEXT) { + convParts.ASR_REPLACEMENT_TEXT.forEach(trans => { + let text = trans.transcriptText; + if (wakeWord && text.startsWith(wakeWord)) { + text = text.substr(wakeWord.length).trim(); + } else if (text.startsWith('alexa')) { + text = text.substr(5).trim(); + } + o.description.summary += `${text}, `; + }); + } + o.description.summary = o.description.summary.substring(0, o.description.summary.length - 2).trim(); + } + o.alexaResponse = ''; + if (convParts.ALEXA_RESPONSE || convParts.TTS_REPLACEMENT_TEXT) { + if (convParts.ALEXA_RESPONSE) { + convParts.ALEXA_RESPONSE.forEach(trans => o.alexaResponse += `${trans.transcriptText}, `); + } + if (convParts.TTS_REPLACEMENT_TEXT) { + convParts.TTS_REPLACEMENT_TEXT.forEach(trans => o.alexaResponse += `${trans.transcriptText}, `); + } + o.alexaResponse = o.alexaResponse.substring(0, o.alexaResponse.length - 2).trim(); + } + if (options.filter) { + if (!o.description || !o.description.summary.length) continue; + + if (res.utteranceType === 'WAKE_WORD_ONLY') { + continue; + } + + switch (o.description.summary) { + case 'stopp': + case 'alexa': + case 'echo': + case 'computer': + case 'amazon': + case 'ziggy': + case ',': + case '': + continue; + } + } + + if (o.description.summary || !options.filter) ret.push(o); + } + } + if (typeof callback === 'function') return callback (err, ret); + }, + { + headers: { + 'authority': `www.${this._options.amazonPage}`, + 'anti-csrftoken-a2z': this.activityCsrfToken, + 'referer': this.activityCsrfTokenReferer + }, + method: 'POST', + data: '{"previousRequestToken": null}' + } + ); + } + + getCustomerHistoryRecords(options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + if (!options.forceRequest && this.activityUpdateQueue && this.activityUpdateQueue.length) { + return callback && callback(new Error('Activity update is running, please try again later.')); + } + + if (this.activityCsrfToken && this.activityCsrfTokenExpiry > Date.now()) { + return this._getCustomerHistoryRecords(options, callback); + } + + const csrfPageUrl = `https://www.${this._options.amazonPage}/alexa-privacy/apd/activity?disableGlobalNav=true&ref=activityHistory`; // disableGlobalNav=true&locale=de-DE + this.httpsGet(csrfPageUrl, (err, result) => { + if (err || !result) return callback && callback(err, result); + + const regex = /meta name="csrf-token" content="([^"]+)"/g; + const csrfTokenRes = regex.exec(result); + if (csrfTokenRes && csrfTokenRes[1]) { + this.activityCsrfToken = csrfTokenRes[1]; + this.activityCsrfTokenExpiry = Date.now() + 2 * 60 * 60 * 1000; + this.activityCsrfTokenReferer = csrfPageUrl; + + this._getCustomerHistoryRecords(options, callback); + } else { + return callback/*.length >= 2*/ && callback(new Error('CSRF Page has no token'), result); + } + }, { + handleAsText: true + }); + } + + getAccount(includeActors, callback) { + if (typeof includeActors === 'function') { + callback = includeActors; + includeActors = false; + } + this.httpsGet(`https://alexa-comms-mobile-service.${this._options.amazonPage}/accounts${includeActors ? '?includeActors=true' : ''}`, callback); + } + + getContacts(options, callback) { + if (typeof options === 'function') { + callback = options; + options = undefined; + } + if (options === undefined) options = {}; + if (options.includePreferencesByLevel === undefined) options.includePreferencesByLevel = 'HomeGroup'; + if (options.includeNonAlexaContacts === undefined) options.includeNonAlexaContacts = true; + if (options.includeHomeGroupMembers === undefined) options.includeHomeGroupMembers = true; + if (options.bulkImportOnly === undefined) options.bulkImportOnly = false; + if (options.includeBlockStatus === undefined) options.includeBlockStatus = false; + if (options.dedupeMode === undefined) options.dedupeMode = 'RemoveCloudOnlyContactDuplicates'; + if (options.homeGroupId === undefined) options.homeGroupId = ''; + + this.httpsGet( + `https://alexa-comms-mobile-service.${this._options.amazonPage}/users/${this.commsId}/contacts + ?includePreferencesByLevel=${options.includePreferencesByLevel} + &includeNonAlexaContacts=${options.includeNonAlexaContacts} + &includeHomeGroupMembers=${options.includeHomeGroupMembers} + &bulkImportOnly=${options.bulkImportOnly} + &includeBlockStatus=${options.includeBlockStatus} + &dedupeMode=${options.dedupeMode} + &homeGroupId=${options.homeGroupId}`, + function (err, result) { + callback (err, result); + }); + } + + getConversations(options, callback) { + if (typeof options === 'function') { + callback = options; + options = undefined; + } + if (options === undefined) options = {}; + if (options.latest === undefined) options.latest = true; + if (options.includeHomegroup === undefined) options.includeHomegroup = true; + if (options.unread === undefined) options.unread = false; + if (options.modifiedSinceDate === undefined) options.modifiedSinceDate = '1970-01-01T00:00:00.000Z'; + if (options.includeUserName === undefined) options.includeUserName = true; + + this.httpsGet( + `https://alexa-comms-mobile-service.${this._options.amazonPage}/users/${this.commsId}/conversations + ?latest=${options.latest} + &includeHomegroup=${options.includeHomegroup} + &unread=${options.unread} + &modifiedSinceDate=${options.modifiedSinceDate} + &includeUserName=${options.includeUserName}`, + function (err, result) { + callback (err, result); + }); + } + + connectBluetooth(serialOrName, btAddress, callback) { + const dev = this.find(serialOrName); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + const flags = { + data: JSON.stringify({ bluetoothDeviceAddress: btAddress}), + method: 'POST' + }; + this.httpsGet(`/api/bluetooth/pair-sink/${dev.deviceType}/${dev.serialNumber}`, callback, flags); + } + + disconnectBluetooth(serialOrName, btAddress, callback) { + const dev = this.find(serialOrName); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + const flags = { + //data: JSON.stringify({ bluetoothDeviceAddress: btAddress}), + method: 'POST' + }; + this.httpsGet(`/api/bluetooth/disconnect-sink/${dev.deviceType}/${dev.serialNumber}`, callback, flags); + } + + setDoNotDisturb(serialOrName, enabled, callback) { + const dev = this.find(serialOrName); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + const flags = { + data: JSON.stringify({ + deviceSerialNumber: dev.serialNumber, + deviceType: dev.deviceType, + enabled: enabled + }), + method: 'PUT' + }; + this.httpsGet(`/api/dnd/status`, callback, flags); + } + + find(serialOrName) { + if (typeof serialOrName === 'object') return serialOrName; + if (!serialOrName) return null; + let dev = this.serialNumbers[serialOrName]; + if (dev !== undefined) return dev; + dev = this.names[serialOrName]; + if (!dev && typeof serialOrName === 'string') dev = this.names [serialOrName.toLowerCase()]; + if (!dev) dev = this.friendlyNames[serialOrName]; + return dev; + } + + setAlarmVolume(serialOrName, volume, callback) { + const dev = this.find(serialOrName); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + const flags = { + data: JSON.stringify ({ + deviceSerialNumber: dev.serialNumber, + deviceType: dev.deviceType, + softwareVersion: dev.softwareVersion, + volumeLevel: volume + }), + method: 'PUT' + }; + this.httpsGet(`/api/device-notification-state/${dev.deviceType}/${dev.softwareVersion}/${dev.serialNumber}`, callback, flags); + } + + sendCommand(serialOrName, command, value, callback) { + return this.sendMessage(serialOrName, command, value, callback); + } + sendMessage(serialOrName, command, value, callback) { + const dev = this.find(serialOrName); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + const commandObj = { contentFocusClientId: null }; + switch (command) { + case 'play': + case 'pause': + case 'next': + case 'previous': + case 'forward': + case 'rewind': + commandObj.type = `${command.substr(0, 1).toUpperCase() + command.substr(1)}Command`; + break; + case 'volume': + commandObj.type = 'VolumeLevelCommand'; + commandObj.volumeLevel = ~~value; + if (commandObj.volumeLevel < 0 || commandObj.volumeLevel > 100) { + return callback(new Error('Volume needs to be between 0 and 100')); + } + break; + case 'shuffle': + commandObj.type = 'ShuffleCommand'; + commandObj.shuffle = (value === 'on' || value === true); + break; + case 'repeat': + commandObj.type = 'RepeatCommand'; + commandObj.repeat = (value === 'on' || value === true); + break; + case 'jump': + commandObj.type = 'JumpCommand'; + commandObj.mediaId = value; + break; + default: + return; + } + + this.httpsGet(`/api/np/command?deviceSerialNumber=${dev.serialNumber}&deviceType=${dev.deviceType}`, + callback, + { + method: 'POST', + data: JSON.stringify(commandObj) + } + ); + } + + createSequenceNode(command, value, serialOrName, overrideCustomerId) { + let deviceSerialNumber = 'ALEXA_CURRENT_DSN'; + let deviceType= 'ALEXA_CURRENT_DEVICE_TYPE'; + let deviceOwnerCustomerId = 'ALEXA_CUSTOMER_ID'; + let deviceAccountId; + let deviceFamily; + let deviceTimezoneId; + if (serialOrName && !Array.isArray(serialOrName)) { + const currDevice = this.find(serialOrName); + if (currDevice) { + deviceSerialNumber = currDevice.serialNumber; + deviceType = currDevice.deviceType; + deviceOwnerCustomerId = currDevice.deviceOwnerCustomerId; + deviceAccountId = currDevice.deviceAccountId; + deviceFamily = currDevice.deviceFamily; + if (currDevice.preferences && currDevice.preferences.timeZoneId !== undefined) { + deviceTimezoneId = currDevice.preferences.timeZoneId; + } + } + } else { + const currDevice = this.find(serialOrName[0]); + if (currDevice) { + if (currDevice.preferences && currDevice.preferences.timeZoneId !== undefined) { + deviceTimezoneId = currDevice.preferences.timeZoneId; + } + } + } + if (overrideCustomerId) { + deviceOwnerCustomerId = overrideCustomerId; + } + const seqNode = { + '@type': 'com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode', + 'operationPayload': { + 'deviceType': deviceType, + 'deviceSerialNumber': deviceSerialNumber, + 'locale': 'ALEXA_CURRENT_LOCALE', + 'customerId': deviceOwnerCustomerId + } + }; + switch (command) { + case 'weather': + seqNode.type = 'Alexa.Weather.Play'; + break; + case 'traffic': + seqNode.type = 'Alexa.Traffic.Play'; + break; + case 'flashbriefing': + seqNode.type = 'Alexa.FlashBriefing.Play'; + break; + case 'goodmorning': + seqNode.type = 'Alexa.GoodMorning.Play'; + break; + case 'funfact': + seqNode.type = 'Alexa.FunFact.Play'; + break; + case 'joke': + seqNode.type = 'Alexa.Joke.Play'; + break; + case 'cleanup': + seqNode.type = 'Alexa.CleanUp.Play'; + break; + case 'singasong': + seqNode.type = 'Alexa.SingASong.Play'; + break; + case 'tellstory': + seqNode.type = 'Alexa.TellStory.Play'; + break; + case 'calendarToday': + seqNode.type = 'Alexa.Calendar.PlayToday'; + break; + case 'calendarTomorrow': + seqNode.type = 'Alexa.Calendar.PlayTomorrow'; + break; + case 'calendarNext': + seqNode.type = 'Alexa.Calendar.PlayNext'; + break; + case 'wait': + seqNode.type = 'Alexa.System.Wait'; + seqNode.operationPayload.waitTimeInSeconds = parseInt(value, 10); + break; + case 'textCommand': + seqNode.type = 'Alexa.TextCommand'; + seqNode.skillId = 'amzn1.ask.1p.tellalexa'; + seqNode.operationPayload.text = value.toString().toLowerCase(); + break; + case 'sound': + seqNode.type = 'Alexa.Sound'; + seqNode.skillId = 'amzn1.ask.1p.sound'; + seqNode.operationPayload.soundStringId = value.toString(); + break; + case 'curatedtts': { + const supportedValues = ['goodbye', 'confirmations', 'goodmorning', 'compliments', 'birthday', 'goodnight', 'iamhome']; + if (!supportedValues.includes(value)) { + return null; + } + seqNode.type = 'Alexa.CannedTts.Speak'; + seqNode.operationPayload.cannedTtsStringId = `alexa.cannedtts.speak.curatedtts-category-${value}/alexa.cannedtts.speak.curatedtts-random`; + break; + } + case 'fireTVTurnOn': + case 'fireTVTurnOff': + case 'fireTVTurnOnOff': + case 'fireTVPauseVideo': + case 'fireTVResumeVideo': + case 'fireTVNavigateHome': + if (!deviceAccountId) { + throw new Error('No deviceAccountId found'); + } + if (!deviceFamily || deviceFamily !== 'FIRE_TV') { + throw new Error('Device is not a Fire TV'); + } + if (command === 'fireTVTurnOnOff') { + command = value ? 'fireTVTurnOn' : 'fireTVTurnOff'; + } + if (command === 'fireTVTurnOn') { + seqNode.type = 'Alexa.Operation.FireTV.TurnOn'; + } else if (command === 'fireTVTurnOff') { + seqNode.type = 'Alexa.Operation.FireTV.TurnOff'; + } else if (command === 'fireTVPauseVideo') { + seqNode.type = 'Alexa.Operation.FireTV.PauseVideo'; + } else if (command === 'fireTVResumeVideo') { + seqNode.type = 'Alexa.Operation.FireTV.ResumeVideo'; + } else if (command === 'fireTVNavigateHome') { + seqNode.type = 'Alexa.Operation.FireTV.NavigateHome'; + } + seqNode.skillId = 'amzn1.ask.1p.routines.firetv'; + seqNode.operationPayload.deviceAccountId = deviceAccountId; + delete seqNode.operationPayload.deviceType; + delete seqNode.operationPayload.deviceSerialNumber; + delete seqNode.operationPayload.locale; + break; + case 'volume': + seqNode.type = 'Alexa.DeviceControls.Volume'; + value = ~~value; + if (value < 0 || value > 100) { + throw new Error('Volume needs to be between 0 and 100'); + } + seqNode.operationPayload.value = value; + seqNode.skillId = 'amzn1.ask.1p.alexadevicecontrols'; + break; + case 'deviceStop': + case 'deviceStopAll': + seqNode.type = 'Alexa.DeviceControls.Stop'; + if (command === 'deviceStopAll') { + seqNode.operationPayload.devices = [ + { + 'deviceSerialNumber': 'ALEXA_ALL_DSN', + 'deviceType': 'ALEXA_ALL_DEVICE_TYPE' + } + ]; + } else { + seqNode.operationPayload.devices = [ + { + 'deviceSerialNumber': deviceSerialNumber, + 'deviceType': deviceType + } + ]; + + if (serialOrName && Array.isArray(serialOrName)) { + seqNode.operationPayload.devices = []; + serialOrName.forEach((deviceId) => { + const currDevice = this.find(deviceId); + if (!currDevice) return; + seqNode.operationPayload.devices.push({ + 'deviceSerialNumber': currDevice.serialNumber, + 'deviceType': currDevice.deviceType + }); + }); + } + } + seqNode.skillId = 'amzn1.ask.1p.alexadevicecontrols'; + seqNode.operationPayload.isAssociatedDevice = false; + delete seqNode.operationPayload.deviceType; + delete seqNode.operationPayload.deviceSerialNumber; + delete seqNode.operationPayload.locale; + break; + case 'deviceDoNotDisturb': + case 'deviceDoNotDisturbAll': { + seqNode.type = 'Alexa.DeviceControls.DoNotDisturb'; + if (command === 'deviceDoNotDisturbAll') { + seqNode.operationPayload.devices = [ + { + 'deviceSerialNumber': 'ALEXA_ALL_DSN', + 'deviceTypeId': 'ALEXA_ALL_DEVICE_TYPE' + } + ]; + } else { + seqNode.operationPayload.devices = [ + { + 'deviceSerialNumber': deviceSerialNumber, + 'deviceTypeId': deviceType, + 'deviceAccountId': deviceAccountId + } + ]; + + if (serialOrName && Array.isArray(serialOrName)) { + seqNode.operationPayload.devices = []; + serialOrName.forEach((deviceId) => { + const currDevice = this.find(deviceId); + if (!currDevice) return; + seqNode.operationPayload.devices.push({ + 'deviceSerialNumber': currDevice.serialNumber, + 'deviceTypeId': currDevice.deviceType, + 'deviceAccountId': currDevice.deviceAccountId + }); + }); + } + } + seqNode.operationPayload.isAssociatedDevice = false; + seqNode.skillId = 'amzn1.ask.1p.alexadevicecontrols'; + seqNode.operationPayload.action = value ? 'Enable' : 'Disable'; + const valueType = typeof value; + if (valueType === 'number' && value <= 12 * 60 * 60) { + const hours = Math.floor(value / 3600); + const minutes = Math.floor((value - hours * 3600) / 60); + const seconds = value - hours * 3600 - minutes * 60; + let durationString = 'DURATION#PT'; + if (hours > 0) { + durationString += `${hours}H`; + } + durationString += `${minutes}M`; + durationString += `${seconds}S`; + seqNode.operationPayload.duration = durationString; + //seqNode.operationPayload.timeZoneId = 'Europe/Berlin'; + } else if (valueType === 'string') { + if (!/^\d{2}:\d{2}$/.test(value)) { + throw new Error('Invalid timepoint value provided'); + } + seqNode.operationPayload.until = `TIME#T${value}`; + seqNode.operationPayload.timeZoneId = deviceTimezoneId || 'Europe/Berlin'; + } else if (valueType !== 'boolean') { + throw new Error('Invalid timepoint provided'); + } + seqNode.operationPayload.isAssociatedDevice = false; + delete seqNode.operationPayload.deviceType; + delete seqNode.operationPayload.deviceSerialNumber; + delete seqNode.operationPayload.locale; + break; + } + case 'speak': + seqNode.type = 'Alexa.Speak'; + if (typeof value !== 'string') value = String(value); + if (!this._options.amazonPage || !this._options.amazonPage.endsWith('.com')) { + value = value.replace(/([^0-9]?[0-9]+)\.([0-9]+[^0-9])?/g, '$1,$2'); + } + /*value = value + .replace(/Â|À|Å|Ã/g, 'A') + .replace(/á|â|à|å|ã/g, 'a') + .replace(/Ä/g, 'Ae') + .replace(/ä/g, 'ae') + .replace(/Ç/g, 'C') + .replace(/ç/g, 'c') + .replace(/É|Ê|È|Ë/g, 'E') + .replace(/é|ê|è|ë/g, 'e') + .replace(/Ó|Ô|Ò|Õ|Ø/g, 'O') + .replace(/ó|ô|ò|õ/g, 'o') + .replace(/Ö/g, 'Oe') + .replace(/ö/g, 'oe') + .replace(/Š/g, 'S') + .replace(/š/g, 's') + .replace(/ß/g, 'ss') + .replace(/Ú|Û|Ù/g, 'U') + .replace(/ú|û|ù/g, 'u') + .replace(/Ü/g, 'Ue') + .replace(/ü/g, 'ue') + .replace(/Ý|Ÿ/g, 'Y') + .replace(/ý|ÿ/g, 'y') + .replace(/Ž/g, 'Z') + .replace(/ž/, 'z') + .replace(/&/, 'und') + .replace(/[^-a-zA-Z0-9_,.?! ]/g,'') + .replace(/ /g,'_');*/ + value = value.replace(/[ ]+/g, ' '); + if (value.length === 0) { + throw new Error('Can not speak empty string', null); + } + if (value.length > 250) { + throw new Error('text too long, limit are 250 characters', null); + } + seqNode.skillId = 'amzn1.ask.1p.saysomething'; + seqNode.operationPayload.textToSpeak = value; + break; + case 'skill': + seqNode.type = 'Alexa.Operation.SkillConnections.Launch'; + if (typeof value !== 'string') value = String(value); + if (value.length === 0) { + throw new Error('Can not launch empty skill', null); + } + seqNode.skillId = value; + seqNode.operationPayload.targetDevice = { + deviceType: seqNode.operationPayload.deviceType, + deviceSerialNumber: seqNode.operationPayload.deviceSerialNumber + }; + seqNode.operationPayload.connectionRequest = { + uri: `connection://AMAZON.Launch/${value}`, + input: {} + }; + seqNode.name = null; + delete seqNode.operationPayload.deviceType; + delete seqNode.operationPayload.deviceSerialNumber; + break; + case 'notification': { + seqNode.type = 'Alexa.Notifications.SendMobilePush'; + let title = 'ioBroker'; + if (value && typeof value === 'object') { + title = value.title || title; + value = value.text || value.value; + } + if (typeof value !== 'string') value = String(value); + if (value.length === 0) { + throw new Error('Can not notify empty string'); + } + seqNode.operationPayload.notificationMessage = value; + seqNode.operationPayload.alexaUrl = '#v2/behaviors'; + seqNode.operationPayload.title = title; + delete seqNode.operationPayload.deviceType; + delete seqNode.operationPayload.deviceSerialNumber; + delete seqNode.operationPayload.locale; + seqNode.skillId = 'amzn1.ask.1p.routines.messaging'; + break; + } + case 'announcement': + case 'ssml': + seqNode.type = 'AlexaAnnouncement'; + if (typeof value !== 'string') value = String(value); + if (command === 'announcement') { + if (!this._options.amazonPage || !this._options.amazonPage.endsWith('.com')) { + value = value.replace(/([^0-9]?[0-9]+)\.([0-9]+[^0-9])?/g, '$1,$2'); + } + value = value.replace(/[ ]+/g, ' '); + if (value.length === 0) { + throw new Error('Can not speak empty string', null); + } + } + else if (command === 'ssml') { + if (!value.startsWith('')) { + throw new Error('Value needs to be a valid SSML XML string', null); + } + } + seqNode.skillId = 'amzn1.ask.1p.routines.messaging'; + seqNode.operationPayload.expireAfter = 'PT5S'; + seqNode.operationPayload.content = [ + { + 'locale': 'de-DE', + 'display': { + 'title': 'ioBroker', + 'body': value.replace(/<[^>]+>/g, '') + }, + 'speak': { + 'type': (command === 'ssml') ? 'ssml' : 'text', + 'value': value + } + } + ]; + seqNode.operationPayload.target = { + 'customerId': deviceOwnerCustomerId, + 'devices': [ + { + 'deviceSerialNumber': deviceSerialNumber, + 'deviceTypeId': deviceType + } + ] + }; + if (serialOrName && Array.isArray(serialOrName)) { + seqNode.operationPayload.target.devices = []; + serialOrName.forEach((deviceId) => { + const currDevice = this.find(deviceId); + if (!currDevice) return; + seqNode.operationPayload.target.devices.push({ + 'deviceSerialNumber': currDevice.serialNumber, + 'deviceTypeId': currDevice.deviceType + }); + }); + } + + delete seqNode.operationPayload.deviceType; + delete seqNode.operationPayload.deviceSerialNumber; + delete seqNode.operationPayload.locale; + break; + default: + return; + } + return seqNode; + } + + buildSequenceNodeStructure(serialOrName, commands, sequenceType, overrideCustomerId) { + if (!sequenceType) sequenceType = 'SerialNode'; // or ParallelNode + + const nodes = []; + for (const command of commands) { + if (command.nodes) { + const subSequence = this.buildSequenceNodeStructure(serialOrName, command.nodes, command.sequenceType || sequenceType, overrideCustomerId); + if (subSequence) { + nodes.push(subSequence); + } + } else { + const commandNode = this.createSequenceNode(command.command, command.value, command.device || serialOrName, overrideCustomerId); + if (commandNode) { + nodes.push(commandNode); + } + } + } + + return { + '@type': `com.amazon.alexa.behaviors.model.${sequenceType}`, + 'name': null, + 'nodesToExecute': nodes + }; + } + + sendMultiSequenceCommand(serialOrName, commands, sequenceType, overrideCustomerId, callback) { + try { + const sequenceObj = { + 'sequence': { + '@type': 'com.amazon.alexa.behaviors.model.Sequence', + 'startNode': this.buildSequenceNodeStructure(serialOrName, commands, sequenceType, overrideCustomerId) + } + }; + + this.sendSequenceCommand(serialOrName, sequenceObj, callback); + } catch (err) { + callback && callback(err, null); + } + } + + sendSequenceCommand(serialOrName, command, value, overrideCustomerId, callback) { + if (typeof overrideCustomerId === 'function') { + callback = overrideCustomerId; + overrideCustomerId = null; + } + + const dev = this.find(Array.isArray(serialOrName) ? serialOrName[0] : serialOrName); + if (!dev && !command.endsWith('All')) return callback && callback(new Error('Unknown Device or Serial number'), null); + + if (typeof value === 'function') { + callback = value; + value = null; + } + + let seqCommandObj; + if (typeof command === 'object') { + seqCommandObj = command.sequence || command; + } + else { + try { + seqCommandObj = { + '@type': 'com.amazon.alexa.behaviors.model.Sequence', + 'startNode': this.createSequenceNode(command, value, serialOrName, overrideCustomerId) + }; + } catch (err) { + return callback && callback(err, null); + } + } + + const reqObj = { + 'behaviorId': seqCommandObj.sequenceId ? command.automationId : 'PREVIEW', + 'sequenceJson': JSON.stringify(seqCommandObj), + 'status': 'ENABLED' + }; + if (dev) { + reqObj.sequenceJson = reqObj.sequenceJson.replace(/"deviceType":"ALEXA_CURRENT_DEVICE_TYPE"/g, `"deviceType":"${dev.deviceType}"`); + reqObj.sequenceJson = reqObj.sequenceJson.replace(/"deviceTypeId":"ALEXA_CURRENT_DEVICE_TYPE"/g, `"deviceTypeId":"${dev.deviceType}"`); + reqObj.sequenceJson = reqObj.sequenceJson.replace(/"deviceSerialNumber":"ALEXA_CURRENT_DSN"/g, `"deviceSerialNumber":"${dev.serialNumber}"`); + reqObj.sequenceJson = reqObj.sequenceJson.replace(/"customerId":"ALEXA_CUSTOMER_ID"/g, `"customerId":"${dev.deviceOwnerCustomerId}"`); + } + reqObj.sequenceJson = reqObj.sequenceJson.replace(/"locale":"ALEXA_CURRENT_LOCALE"/g, `"locale":"de-DE"`); + + this.httpsGet(`/api/behaviors/preview`, + callback, + { + method: 'POST', + data: JSON.stringify(reqObj) + } + ); + } + + getAutomationRoutines(limit, callback) { + if (typeof limit === 'function') { + callback = limit; + limit = 0; + } + limit = limit || 2000; + this.httpsGet(`/api/behaviors/v2/automations?limit=${limit}`, callback, { + timeout: 30000 + }); + } + + executeAutomationRoutine(serialOrName, routine, callback) { + return this.sendSequenceCommand(serialOrName, routine, callback); + } + + /** + * Get the Skill catalog that can be used for routines + * + * @param catalogId string defaults to "Root" + * @param limit number defaults to 100 + * @param callback response callback + */ + getRoutineSkillCatalog(catalogId, limit, callback) { + if (typeof limit === 'function') { + callback = limit; + limit = 100; + } + if (typeof catalogId === 'function') { + callback = catalogId; + catalogId = 'Root'; + } + + // request options + const request = { + 'method': 'POST', + 'data': JSON.stringify({ + actions: [], + triggers: [{ + skill: 'amzn1.ask.1p.customutterance', + type: 'CustomUtterance' + }], + limit + }) + }; + + // send request + this.httpsGet(`/api/routines/catalog/action/${catalogId}`, callback, request); + } + + getMusicProviders(callback) { + this.httpsGet('/api/behaviors/entities?skillId=amzn1.ask.1p.music', + callback, + { + headers: { + 'Routines-Version': '3.0.264101' + } + } + ); + } + + playMusicProvider(serialOrName, providerId, searchPhrase, callback) { + const dev = this.find(serialOrName); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + if (searchPhrase === '') return callback && callback(new Error('Searchphrase empty'), null); + + const operationPayload = { + 'deviceType': dev.deviceType, + 'deviceSerialNumber': dev.serialNumber, + 'locale': 'de-DE', // TODO!! + 'customerId': dev.deviceOwnerCustomerId, + 'musicProviderId': providerId, + 'searchPhrase': searchPhrase + }; + + const validateObj = { + 'type': 'Alexa.Music.PlaySearchPhrase', + 'operationPayload': JSON.stringify(operationPayload) + }; + + this.httpsGet(`/api/behaviors/operation/validate`, + (err, res) => { + if (err) { + return callback && callback(err, res); + } + if (res.result !== 'VALID') { + return callback && callback(new Error('Request invalid'), res); + } + validateObj.operationPayload = res.operationPayload; + + const seqCommandObj = { + '@type': 'com.amazon.alexa.behaviors.model.Sequence', + 'startNode': validateObj + }; + seqCommandObj.startNode['@type'] = 'com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode'; + + return this.sendSequenceCommand(serialOrName, seqCommandObj, callback); + }, + { + method: 'POST', + data: JSON.stringify(validateObj) + } + ); + } + + playAudible(serialOrName, searchPhrase, callback) { + const dev = this.find(serialOrName); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + if (searchPhrase === '') return callback && callback(new Error('Searchphrase empty'), null); + + const operationPayload = { + 'deviceType': dev.deviceType, + 'deviceSerialNumber': dev.serialNumber, + 'locale': 'de', // TODO!! + 'customerId': dev.deviceOwnerCustomerId, + 'searchPhrase': searchPhrase + }; + + const validateObj = { + 'type': 'Alexa.Audible.Read', + 'operationPayload': JSON.stringify(operationPayload) + }; + + this.httpsGet(`/api/behaviors/operation/validate`, + (err, res) => { + if (err) { + return callback && callback(err, res); + } + if (res.result !== 'VALID') { + return callback && callback(new Error('Request invalid'), res); + } + validateObj.operationPayload = res.operationPayload; + + const seqCommandObj = { + '@type': 'com.amazon.alexa.behaviors.model.Sequence', + 'startNode': validateObj + }; + seqCommandObj.startNode['@type'] = 'com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode'; + seqCommandObj.startNode.skillId = 'amzn1.ask.skill.3b150b52-cedb-4792-a4b2-f656523a06f5'; + + return this.sendSequenceCommand(serialOrName, seqCommandObj, callback); + }, + { + method: 'POST', + data: JSON.stringify(validateObj) + } + ); + } + + /*playFireTV(serialOrName, searchPhrase, callback) { + const dev = this.find(serialOrName); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + if (searchPhrase === '') return callback && callback(new Error('Searchphrase empty'), null); + + if (dev.deviceFamily !== 'FIRE_TV') { + return callback && callback(new Error('Device is not a FireTV'), null); + } + + const operationPayload = { + //'deviceType': dev.deviceType, + //'deviceSerialNumber': dev.serialNumber, + 'locale': 'de', // TODO!! + 'customerId': dev.deviceOwnerCustomerId, + 'deviceAccountId': dev.deviceAccountId, + 'searchPhrase': searchPhrase, + 'speakerId': 'ALEXA_CURRENT_SPEAKER_ID' + }; + + const validateObj = { + 'type': 'Alexa.Operation.Video.PlaySearchPhrase', + 'operationPayload': JSON.stringify(operationPayload) + }; + + this.httpsGet(`/api/behaviors/operation/validate`, + (err, res) => { + if (err) { + return callback && callback(err, res); + } + if (res.result !== 'VALID') { + return callback && callback(new Error('Request invalid'), res); + } + validateObj.operationPayload = res.operationPayload; + + const seqCommandObj = { + '@type': 'com.amazon.alexa.behaviors.model.Sequence', + 'startNode': validateObj + }; + seqCommandObj.startNode['@type'] = 'com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode'; + seqCommandObj.startNode.skillId = 'amzn1.ask.skill.3b150b52-cedb-4792-a4b2-f656523a06f5'; + + return this.sendSequenceCommand(serialOrName, seqCommandObj, callback); + }, + { + method: 'POST', + data: JSON.stringify(validateObj) + } + ); + }*/ + + sendTextMessage(conversationId, text, callback) { + // [{ + // "conversationId": "amzn1.comms.messaging.id.conversationV2~e48ea7a9-b358-44fa-9be4-e45ae6a37c6a", + // "clientMessageId": "36772d6a-c2ba-4294-955f-afc3336a444c", + // "messageId": 1.001, + // "time": "2019-07-18T21:32:26.863Z", + // "sender": "amzn1.comms.id.person.amzn1~amzn1.account.AEQ4CW5IVBICJ5PQNYI5RYKBSDXQ", + // "type": "message/text", + // "payload": { + // "text": "Test atest" + // }, + // "status": 1 + // }] + + const message = [{ + conversationId: `amzn1.comms.messaging.id.conversationV2~${uuidv1()}`, + clientMessageId: uuidv1(), + messageId: 0.001, + time: new Date().toISOString(), + sender: this.commsId, + type: 'message/text', + payload: { + text: text + }, + status: 1 + }]; + + this.httpsGet(`https://alexa-comms-mobile-service.${this._options.amazonPage}/users/${this.commsId}/conversations/${conversationId}/messages`, + callback, + { + method: 'POST', + data: JSON.stringify (message) + } + ); + } + + deleteConversation(conversationId, lastMessageId, callback) { + if (lastMessageId === undefined) { + lastMessageId = 1; + } + const flags = { + method: 'DELETE' + }; + this.httpsGet(`https://alexa-comms-mobile-service.${this._options.amazonPage}/users/${this.commsId}/conversations/${conversationId}?lastMessageId=${lastMessageId}`, callback, flags); + } + + setReminder(serialOrName, timestamp, label, callback) { + const notification = this.createNotificationObject(serialOrName, 'Reminder', label, new Date(timestamp)); + this.createNotification(notification, callback); + } + + getHomeGroup(callback) { + this.httpsGet(`https://alexa-comms-mobile-service.${this._options.amazonPage}/users/${this.commsId}/identities?includeUserName=true`, callback); + } + + getDevicePreferences(serialOrName, callback) { + if (typeof serialOrName === 'function') { + callback = serialOrName; + serialOrName = null; + } + this.httpsGet('/api/device-preferences?cached=true', (err, res) => { + if (serialOrName) { + const device = this.find(serialOrName); + if (!device) { + return callback && callback(new Error('Unknown Device or Serial number'), null); + } + + if (!err && res && res.devicePreferences && Array.isArray(res.devicePreferences)) { + const devicePreferences = res.devicePreferences.filter(pref => pref.deviceSerialNumber === device.serialNumber); + if (devicePreferences.length > 0) { + return callback && callback(null, devicePreferences[0]); + } else { + return callback && callback(new Error(`No Device Preferences found for ${device.serialNumber}`), null); + } + } + } + callback && callback(err, res); + }); + } + + setDevicePreferences(serialOrName, preferences, callback) { + const device = this.find(serialOrName); + if (!device) { + return callback && callback(new Error('Unknown Device or Serial number'), null); + } + + this.httpsGet(`/api/device-preferences/${device.serialNumber}`, callback, { + method: 'PUT', + data: JSON.stringify(preferences) + }); + } + + getDeviceWifiDetails(serialOrName, callback) { + const dev = this.find(serialOrName); + if (!dev) { + return callback && callback(new Error('Unknown Device or Serial number'), null); + } + this.httpsGet(`/api/device-wifi-details?deviceSerialNumber=${dev.serialNumber}&deviceType=${dev.deviceType}`, callback); + } + + getAllDeviceVolumes(callback) { + this.httpsGet('/api/devices/deviceType/dsn/audio/v1/allDeviceVolumes', callback); + } + + /** @deprecated Use getSmarthomeDevicesV2 instead */ + getSmarthomeDevices(callback) { + this.getSmarthomeDevicesV2((err, devices) => { + if (err) { + return callback && callback(err); + } + const mappedResults = devices.map(device => ({ + ...device, + ...device.legacyAppliance, + ...device.legacyIdentifiers, + legacyAppliance: undefined, + legacyIdentifiers: undefined, + })); + const amazonBridgeDetails = {}; + mappedResults.forEach(device => { + const id = device.applianceId; + if (!id) { + this._options.logger && this._options.logger(`Ignoring device without applianceId: ${JSON.stringify(device)}`); + return; + } + if ( + device && + device.dmsIdentifier && + device.dmsIdentifier.deviceType && + device.dmsIdentifier.deviceType.value && + ( + device.dmsIdentifier.deviceType.value.text === 'A2IVLV5VM2W81' || + device.dmsIdentifier.deviceType.value.text === 'A2TF17PFR55MTB' + ) + ) { + return; // Ignore Alexa App devices + } + + amazonBridgeDetails[`LambdaBridge_${id}`] = amazonBridgeDetails[`LambdaBridge_${id}`] || { + applianceDetails: { + applianceDetails: {} + } + }; + amazonBridgeDetails[`LambdaBridge_${id}`].applianceDetails.applianceDetails[id] = device; + }); + const result = { + locationDetails: { + Default_Location: { + locationId: 'Default_Location', + amazonBridgeDetails: { + amazonBridgeDetails + } + } + } + }; + callback && callback(null, result); + }); + } + + getSmarthomeDevicesV2(callback) { + const request = { + method: 'POST', + data: JSON.stringify({ query: SmartHomeEndpointsGraphQlQuery}), + timeout: 30000 + }; + + this.httpsGet('/nexus/v1/graphql', function (err, res) { + if (!err && (!res.data || !res.data.endpoints || !Array.isArray(res.data.endpoints.items))) { + err = new Error('Invalid response from server'); + } + if (err) { + return callback && callback(err, res); + } + callback && callback (null, res.data.endpoints.items); + }, request); + } + + getSmarthomeGroups(callback) { + this.httpsGet('/api/phoenix/group?_=%t', callback); + } + + getSmarthomeEntities(callback) { + this.httpsGet('/api/behaviors/entities?skillId=amzn1.ask.1p.smarthome', + callback, + { + headers: { + 'Routines-Version': '3.0.264101' + }, + timeout: 30000 + } + ); + } + + getFireTVEntities(callback) { + this.httpsGet('/api/behaviors/entities?skillId=amzn1.ask.1p.routines.firetv', + callback, + { + headers: { + 'Routines-Version': '3.0.264101' + }, + timeout: 30000 + } + ); + } + + getSmarthomeBehaviourActionDefinitions(callback) { + this.httpsGet('/api/behaviors/actionDefinitions?skillId=amzn1.ask.1p.smarthome', + callback, + { + headers: { + 'Routines-Version': '3.0.264101' + }, + timeout: 30000 + } + ); + } + + getRoutineSoundList(callback) { + this.httpsGet('/api/behaviors/entities?skillId=amzn1.ask.1p.sound', + callback, + { + headers: { + 'Routines-Version': '3.0.264101' + }, + timeout: 30000 + } + ); + } + + renameDevice(serialOrName, newName, callback) { + const dev = this.find(serialOrName); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + const o = { + accountName: newName, + serialNumber: dev.serialNumber, + deviceAccountId: dev.deviceAccountId, + deviceType: dev.deviceType, + //deviceOwnerCustomerId: oo.deviceOwnerCustomerId + }; + this.httpsGet(`/api/devices-v2/device/${dev.serialNumber}`, + callback, + { + method: 'PUT', + data: JSON.stringify (o), + } + ); + } + + deleteSmarthomeDevice(smarthomeDevice, callback) { + const flags = { + method: 'DELETE' + //data: JSON.stringify (o), + }; + this.httpsGet(`/api/phoenix/appliance/${smarthomeDevice}`, callback, flags); + } + + setEnablementForSmarthomeDevice(smarthomeDevice, enabled, callback) { + const flags = { + method: 'PUT', + data: JSON.stringify ({ + 'applianceId': smarthomeDevice, + 'enabled': !!enabled + }), + }; + this.httpsGet(`/api/phoenix/v2/appliance/${smarthomeDevice}/enablement`, callback, flags); + } + + deleteSmarthomeGroup(smarthomeGroup, callback) { + const flags = { + method: 'DELETE' + //data: JSON.stringify (o), + }; + this.httpsGet(`/api/phoenix/group/${smarthomeGroup}`, callback, flags); + } + + deleteAllSmarthomeDevices(callback) { + const flags = { + method: 'DELETE' + //data: JSON.stringify (o), + }; + this.httpsGet(`/api/phoenix`, callback, flags); + } + + discoverSmarthomeDevice(callback) { + const flags = { + method: 'POST' + //data: JSON.stringify (o), + }; + this.httpsGet('/api/phoenix/discovery', callback, flags); + } + + querySmarthomeDevices(toQuery, entityType, maxTimeout, callback) { + if (typeof maxTimeout === 'function') { + callback = maxTimeout; + maxTimeout = null; + } + if (typeof entityType === 'function') { + callback = entityType; + entityType = 'APPLIANCE'; // other value 'GROUP', 'ENTITY' + } + + const reqArr = []; + if (!Array.isArray(toQuery)) toQuery = [toQuery]; + for (const id of toQuery) { + if (!id) continue; + if (typeof id === 'object') { + reqArr.push(id); + } else { + reqArr.push({ + 'entityId': id, + 'entityType': entityType + }); + } + } + + const flags = { + method: 'POST', + data: JSON.stringify ({ + 'stateRequests': reqArr + }), + timeout: Math.min(maxTimeout || 60000, Math.max(15000, toQuery.length * 200)) + }; + + + this.httpsGet(`/api/phoenix/state`, callback, flags); + /* + { + 'stateRequests': [ + { + 'entityId': 'AAA_SonarCloudService_00:17:88:01:04:1D:4C:A0', + 'entityType': 'APPLIANCE' + } + ] + } + { + 'deviceStates': [], + 'errors': [{ + 'code': 'ENDPOINT_UNREACHABLE', + 'data': null, + 'entity': { + 'entityId': 'AAA_SonarCloudService_00:17:88:01:04:1D:4C:A0', + 'entityType': '' + }, + 'message': null + }] + } + */ + } + + executeSmarthomeDeviceAction(entityIds, parameters, entityType, callback) { + if (typeof entityType === 'function') { + callback = entityType; + entityType = 'APPLIANCE'; // other value 'GROUP' + } + + const reqArr = []; + if (!Array.isArray(entityIds)) entityIds = [entityIds]; + for (const id of entityIds) { + reqArr.push({ + 'entityId': id, + 'entityType': entityType, + 'parameters': parameters + }); + } + + const flags = { + method: 'PUT', + data: JSON.stringify ({ + 'controlRequests': reqArr + }) + }; + this.httpsGet(`/api/phoenix/state`, callback, flags); + /* + { + 'controlRequests': [ + { + 'entityId': 'bbd72582-4b16-4d1f-ab1b-28a9826b6799', + 'entityType':'APPLIANCE', + 'parameters':{ + 'action':'turnOn' + } + } + ] + } + { + 'controlResponses': [], + 'errors': [{ + 'code': 'ENDPOINT_UNREACHABLE', + 'data': null, + 'entity': { + 'entityId': 'bbd72582-4b16-4d1f-ab1b-28a9826b6799', + 'entityType': 'APPLIANCE' + }, + 'message': null + }] + } + */ + } + + + unpaireBluetooth(serialOrName, btAddress, callback) { + const dev = this.find(serialOrName); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + const flags = { + method: 'POST', + data: JSON.stringify ({ + bluetoothDeviceAddress: btAddress, + bluetoothDeviceClass: 'OTHER' + }) + }; + this.httpsGet(`/api/bluetooth/unpair-sink/${dev.deviceType}/${dev.serialNumber}`, callback, flags); + } + + deleteDevice(serialOrName, callback) { + const dev = this.find(serialOrName, callback); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + const flags = { + method: 'DELETE', + data: JSON.stringify ({ + deviceType: dev.deviceType + }) + }; + this.httpsGet(`/api/devices/device/${dev.serialNumber}?deviceType=${dev.deviceType}`, callback, flags); + } + + getDeviceSettings(serialOrName, settingName, callback) { + const dev = this.find(serialOrName, callback); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + this.httpsGet(`/api/v1/devices/${dev.deviceAccountId}/settings/${settingName}`, (err, res) => { + if (!err && res && typeof res.value === 'string') { + try { + res.value = JSON.parse(res.value); + } catch (err) { + // ignore + return callback && callback(new Error(`Invalid value for setting ${settingName}: ${res}`)); + } + } + callback && callback(err, res && res.value); + }); + } + + setDeviceSettings(serialOrName, settingName, value, callback) { + const dev = this.find(serialOrName, callback); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + const flags = { + method: 'PUT', + data: JSON.stringify ({ + value: JSON.stringify(value) + }) + }; + this.httpsGet(`/api/v1/devices/${dev.deviceAccountId}/settings/${settingName}`, callback, flags); + } + + getConnectedSpeakerOptionSetting(serialOrName, callback) { + this.getDeviceSettings(serialOrName, 'connectedSpeakerOption', callback); + } + + setConnectedSpeakerOptionSetting(serialOrName, speakerType, callback) { + this.setDeviceSettings(serialOrName, 'connectedSpeakerOption', speakerType, callback); + } + + getAttentionSpanSetting(serialOrName, callback) { + this.getDeviceSettings(serialOrName, 'attentionSpan', callback); + } + + setAttentionSpanSetting(serialOrName, enabled, callback) { + this.setDeviceSettings(serialOrName, 'attentionSpan', !!enabled, callback); + } + + getAlexaGesturesSetting(serialOrName, callback) { + this.getDeviceSettings(serialOrName, 'alexaGestures', callback); + } + + setAlexaGesturesSetting(serialOrName, enabled, callback) { + this.setDeviceSettings(serialOrName, 'alexaGestures', !!enabled, callback); + } + + getDisplayPowerSetting(serialOrName, callback) { + this.getDeviceSettings(serialOrName, 'displayPower', (err, res) => { + if (!err && res !== undefined) { + res = res === 'ON'; + } + callback && callback(err, res); + }); + } + + setDisplayPowerSetting(serialOrName, enabled, callback) { + this.setDeviceSettings(serialOrName, 'displayPower', enabled ? 'ON' : 'OFF', callback); + } + + getAdaptiveBrightnessSetting(serialOrName, callback) { + this.getDeviceSettings(serialOrName, 'adaptiveBrightness', (err, res) => { + if (!err && res !== undefined) { + res = res === 'ON'; + } + callback && callback(err, res); + }); + } + + setAdaptiveBrightnessSetting(serialOrName, enabled, callback) { + this.setDeviceSettings(serialOrName, 'adaptiveBrightness', enabled ? 'ON' : 'OFF', callback); + } + + getClockTimeFormatSetting(serialOrName, callback) { + this.getDeviceSettings(serialOrName, 'timeFormat', callback); + } + + setClockTimeFormatSetting(serialOrName, format, callback) { + this.setDeviceSettings(serialOrName, 'timeFormat', format, callback); + } + + getBrightnessSetting(serialOrName, callback) { + this.getDeviceSettings(serialOrName, 'brightness', callback); + } + + setBrightnessSetting(serialOrName, brightness, callback) { + if (typeof brightness !== 'number') { + return callback && callback(new Error('Brightness must be a number'), null); + } + if (brightness < 0 || brightness > 100) { + return callback && callback(new Error('Brightness must be between 0 and 100'), null); + } + this.setDeviceSettings(serialOrName, 'brightness', brightness, callback); + } + + /** + * Response: + * { + * "enabled": true + * } + */ + getEqualizerEnabled(serialOrName, callback) { + const dev = this.find(serialOrName, callback); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + this.httpsGet(`/api/equalizer/enabled/${dev.serialNumber}/${dev.deviceType}`, callback); + } + + /** + * Response: + * { + * "max": 6, + * "min": -6 + * } + */ + getEqualizerRange(serialOrName, callback) { + const dev = this.find(serialOrName, callback); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + this.httpsGet(`/api/devices/${dev.serialNumber}/${dev.deviceType}/audio/v1/getEQRange`, callback); + } + + /** + * Response: + * { + * "bass": 0, + * "mid": 0, + * "treble": 0 + * } + */ + getEqualizerSettings(serialOrName, callback) { + const dev = this.find(serialOrName, callback); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + this.httpsGet(`/api/equalizer/${dev.serialNumber}/${dev.deviceType}`, callback); + } + + setEqualizerSettings(serialOrName, bass, midrange, treble, callback) { + const dev = this.find(serialOrName); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + if (bass !== undefined && typeof bass !== 'number') { + return callback && callback(new Error('Bass value must be a number'), null); + } + if (midrange !== undefined && typeof midrange !== 'number') { + return callback && callback(new Error('Midrange value must be a number'), null); + } + if (treble !== undefined && typeof treble !== 'number') { + return callback && callback(new Error('Treble value must be a number'), null); + } + if (bass === undefined && midrange === undefined && treble === undefined) { + return callback && callback(new Error('At least one value must be provided'), null); + } + + const flags = { + method: 'POST', + data: JSON.stringify ({ + bass, + mid: midrange, + treble + }) + }; + this.httpsGet(`/api/equalizer/${dev.serialNumber}/${dev.deviceType}`, callback, flags); + } + + /** + * Response: + * { + * "ports": [{ + * "direction": "OUTPUT", + * "id": "aux0", + * "inputActivity": null, + * "isEnabled": false, + * "isPlugged": false + * }] + * } + */ + getAuxControllerState(serialOrName, callback) { + const dev = this.find(serialOrName, callback); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + this.httpsGet(`/api/auxcontroller/${dev.deviceType}/${dev.serialNumber}/state`, callback); + } + + setAuxControllerPortDirection(serialOrName, direction, port, callback) { + if (typeof port === 'function') { + callback = port; + port = 'aux0'; + } + const dev = this.find(serialOrName, callback); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + const flags = { + method: 'POST', + data: JSON.stringify ({ + direction, + deviceId: dev.serialNumber + }) + }; + this.httpsGet(`/api/auxcontroller/${dev.deviceType}/${dev.serialNumber}/ports/${port}/setDirection`, callback, flags); + } + + getPlayerQueue(serialOrName, size, callback) { + if (typeof size === 'function') { + callback = size; + size = 50; + } + const dev = this.find(serialOrName, callback); + if (!dev) return callback && callback(new Error('Unknown Device or Serial number'), null); + + + this.httpsGet(`/api/np/queue?deviceSerialNumber=${dev.serialNumber}&deviceType=${dev.deviceType}&size=${size}&_=%t`, callback); + } +} + +module.exports = AlexaRemote; diff --git a/alexa-wsmqtt.js b/alexa-wsmqtt.js new file mode 100755 index 00000000..b2cbdfd1 --- /dev/null +++ b/alexa-wsmqtt.js @@ -0,0 +1,862 @@ +const WebSocket = require('ws'); +const EventEmitter = require('events'); +const crypto = require('crypto'); + +class AlexaWsMqtt extends EventEmitter { + + constructor(options, cookie, macDms) { + super(); + + this._options = options; + this.stop = false; + let serialArr = null; + this.cookie = cookie; + this.macDms = macDms; + if (cookie) serialArr = cookie.match(/ubid-[a-z]+=([^;]+);/); + if (!serialArr || !serialArr[1]) { + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Cookie incomplete : ' + JSON.stringify(serialArr)); + return undefined; + } + this.accountSerial = serialArr[1]; + this.websocket = null; + this.pingPongInterval = null; + this.errorRetryCounter = 0; + this.reconnectTimeout = null; + this.pongTimeout = null; + this.initTimeout = null; + this.connectionActive = false; + + this.messageId = Math.floor(1E9 * Math.random()); + } + + isConnected() { + return this.connectionActive; + } + + connectType1() { + const urlTime = Date.now(); + let amazonPage = '.' + this._options.amazonPage; + if (amazonPage === '.amazon.com') amazonPage = '-js.amazon.com'; // Special Handling for US! + const url = `wss://dp-gw-na${amazonPage}/?x-amz-device-type=ALEGCNGL9K0HM&x-amz-device-serial=${this.accountSerial}-${urlTime}`; + + this.websocket = new WebSocket(url, [], + { + 'perMessageDeflate': true, + 'protocolVersion': 13, + + 'timeout': 5000, + + 'headers': { + 'Connection': 'keep-alive, Upgrade', + 'Upgrade': 'websocket', + 'Host': 'dp-gw-na.' + this._options.amazonPage, + 'Origin': 'https://alexa.' + this._options.amazonPage, + 'Pragma': 'no-cache', + 'Cache-Control': 'no-cache', + //'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + //'Accept-Language': 'de,en-US;q=0.7,en;q=0.3', + //'Sec-WebSocket-Key': 'aV/ud2q+G4pTtOhlt/Amww==', + //'Sec-WebSocket-Extensions': 'permessage-deflate', // 'x-webkit-deflate-frame', + //'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15G77 PitanguiBridge/2.2.219248.0-[HARDWARE=iPhone10_4][SOFTWARE=11.4.1]', + 'Cookie': this.cookie, + } + } + ); + return url; + } + + /** + * @param method The http request method (GET, POST, DELETE, ...). + * @param path The requested http url path and query. + * @param body The http message body. + * @return {string} + */ + createRequestSignature(method, path, body) { + /* + date = datetime.utcnow().isoformat("T") + "Z" + body = body.decode("utf-8") + + data = f"{method}\n{path}\n{date}\n{body}\n{adp_token}" + + key = rsa.PrivateKey.load_pkcs1(private_key.encode("utf-8")) + cipher = rsa.pkcs1.sign(data.encode(), key, "SHA-256") + signed_encoded = base64.b64encode(cipher) + + signature = f"{signed_encoded.decode()}:{date}" + + return { + "x-adp-token": adp_token, + "x-adp-alg": "SHA256withRSA:1.0", + "x-adp-signature": signature + } + + :2021-10-08T23:49:33Z + */ + const now = new Date().toISOString(); + + const sign = crypto.createSign('SHA256'); + sign.write(method + '\n'); + sign.write(path + '\n'); + sign.write(now + '\n'); + sign.write(body.toString('utf-8') + '\n'); + sign.write(this.macDms.adp_token); + sign.end(); + + const privateKey = '-----BEGIN PRIVATE KEY-----\n' + this.macDms.device_private_key + '\n-----END PRIVATE KEY-----'; + + return `${sign.sign(privateKey, 'base64')}:${now}`; + } + + connectType2() { + let amazonPage = '.' + this._options.amazonPage; + if (amazonPage === '.amazon.com') amazonPage = '-js.amazon.com'; // Special Handling for US! + const url = `wss://dp-gw-na${amazonPage}/tcomm/`; + + this.websocket = new WebSocket(url, [], + { + 'perMessageDeflate': true, + 'protocolVersion': 13, + + 'timeout': 5000, + + 'headers': { + 'Connection': 'keep-alive, Upgrade', + 'Upgrade': 'websocket', + 'Host': 'dp-gw-na.' + this._options.amazonPage, + 'Origin': 'https://alexa.' + this._options.amazonPage, + 'Pragma': 'no-cache', + 'Cache-Control': 'no-cache', + //'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + //'Accept-Language': 'de,en-US;q=0.7,en;q=0.3', + //'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15G77 PitanguiBridge/2.2.219248.0-[HARDWARE=iPhone10_4][SOFTWARE=11.4.1]', + 'Cookie': this.cookie, + 'x-dp-comm-tuning': 'A:F;A:H', + 'x-dp-reason': 'ClientInitiated;1', + 'x-dp-tcomm-purpose': 'Regular', + //'x-dp-deviceVersion': 'motorola/osprey_reteu_2gb/osprey_u2:6.0.1/MPI24.107-55/33:user/release-keys', + //'x-dp-networkType': 'WIFI', + //'x-dp-tcomm-versionCode': '894920010', + //'x-dp-oui': 'dca632', + 'x-dp-obfuscatedBssid': '-2019514039', + 'x-dp-tcomm-versionName': '2.2.443692.0', + 'x-adp-signature': this.createRequestSignature('GET', '/tcomm/', ''), + 'x-adp-token': this.macDms.adp_token, + 'x-adp-alg': 'SHA256WithRSA:1.0', + } + } + ); + return url; + } + + sendWs(data) { + return new Promise(resolve => { + if (!this.websocket) { + resolve(); + return; + } + this.websocket.send(data, () => resolve()); + }); + } + + wait(delay) { + return new Promise(resolve => { + setTimeout(() => resolve(), delay); + }); + } + + connect() { + let url; + try { + if (this._options.usePushConnectType !== undefined) { + this._options.logger && this._options.logger(`Alexa-Remote WS-MQTT: Try to initialize push connection with type ${this._options.usePushConnectType}`); + if (this._options.usePushConnectType === 1) { + url = this.connectType1(); + } else if (this._options.usePushConnectType === 2) { + url = this.connectType2(); + } else { + throw new Error(`Invalid push connect type ${this._options.usePushConnectType} specified`); + } + } + else if (!this.macDms || !this.macDms.adp_token || !this.macDms.device_private_key) { + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Try to initialize old style push connection because macDms data missing'); + url = this.connectType1(); + } else { + url = this.connectType2(); + } + } + catch (err) { + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Error on Init ' + err.message); + this._options.logger && this._options.logger(err.stack); + this.emit('error', err); + return; + } + let msgCounter = 0; + this.initTimeout && clearTimeout(this.initTimeout); + + const onWebsocketClose = (code, reason) => { + if (reason) { + reason = reason.toString(); + } + this.websocket = null; + this.connectionActive = false; + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Close: ' + code + ': ' + reason); + if (this.initTimeout) { + clearTimeout(this.initTimeout); + this.initTimeout = null; + } + if (this.pingPongInterval) { + clearInterval(this.pingPongInterval); + this.pingPongInterval = null; + } + if (this.pongTimeout) { + clearTimeout(this.pongTimeout); + this.pongTimeout = null; + } + if (code === 4001 && reason.startsWith('before - Could not find any')) { // code = 40001, reason = "before - Could not find any vali" + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Cookie invalid!'); + this.emit('disconnect', false, 'Cookie invalid'); + return; + } + if (this.stop) return; + if (this.errorRetryCounter > 100) { + this.emit('disconnect', false, 'Too many failed retries. Check cookie and data'); + return; + } else { + this.errorRetryCounter++; + } + + const retryDelay = Math.min(60, (this.errorRetryCounter * 5) + 5); + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Retry Connection in ' + retryDelay + 's'); + this.emit('disconnect', true, 'Retry Connection in ' + retryDelay + 's'); + this.reconnectTimeout && clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null; + this.connect(); + }, retryDelay * 1000); + }; + + this.initTimeout = setTimeout(() => { + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Initialization not done within 30s'); + try { + this.websocket && this.websocket.close(); + } catch (err) { + //just make sure + } + if (this.websocket || !this.reconnectTimeout) { // seems no close was emmitted so far?! + onWebsocketClose(); + } + }, 30000); + + this.websocket.on('open', () => { + if (! this.websocket) return; + if (this.stop) { + this.websocket.close(); + return; + } + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Open: ' + url); + this.connectionActive = false; + + if (!this.macDms) { + // tell Tuning Service that we support "A:H" protocol = AlphaPrococol + const msg = Buffer.from('0x99d4f71a 0x0000001d A:HTUNE'); + //console.log('SEND: ' + msg.toString('ascii')); + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: A:H Initialization Msg 1 sent'); + if (this.websocket.readyState !== 1 /* OPEN */) return; + this.websocket.send(msg); + } + }); + + this.websocket.on('close', onWebsocketClose); + + this.websocket.on('error', (error) => { + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Error: ' + error); + this.emit('error', error); + this.websocket && this.websocket.terminate(); + }); + + this.websocket.on('unexpected-response', () => { + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Unexpected Response'); + }); + + this.websocket.on('message', async (data) => { + if (!this.websocket || this.websocket.readyState !== 1 /* OPEN */) { + return; + } + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Incoming RAW message: ' + data.toString('hex')); + const message = this.parseIncomingMessage(data); + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Incoming message: ' + JSON.stringify(message)); + + if (msgCounter === 0) { // initialization + if (message.content.protocolName) { + this.protocolName = message.content.protocolName; + if (this.protocolName !== 'A:H' && this.protocolName !== 'A:F') { + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Server requests unknown protocol: ' + this.protocolName); + } + } + else { + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Unexpected Response: ' + JSON.stringify(message)); + this.protocolName = this.macDms ? 'A:F' : 'A:H'; + } + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Detected protocol ' + this.protocolName); + + let msg; + if (this.protocolName === 'A:F') { // A:F + msg = Buffer.from('0xfe88bc52 0x0000009c {"protocolName":"A:F","parameters":{"AlphaProtocolHandler.receiveWindowSize":"16","AlphaProtocolHandler.maxFragmentSize":"16000"}}TUNE'); + await this.sendWs(msg); + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: A:F Initialization Msg 2 sent: ' + msg.toString('hex')); + + await this.wait(50); + msg = this.encodeGWRegisterAF(); + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: A:F Initialization Msg 3 (Register Connection) sent: ' + msg.toString('hex')); + //console.log('SEND: ' + msg.toString('ascii')); + await this.sendWs(msg); + msgCounter++; + } else { // A:H + msg = Buffer.from('0xa6f6a951 0x0000009c {"protocolName":"A:H","parameters":{"AlphaProtocolHandler.receiveWindowSize":"16","AlphaProtocolHandler.maxFragmentSize":"16000"}}TUNE'); + //console.log('SEND: ' + msg.toString('ascii')); + await this.sendWs(msg); + + await this.wait(50); + //msg = new Buffer('MSG 0x00000361 0x0e414e45 f 0x00000001 0xd7c62f29 0x0000009b INI 0x00000003 1.0 0x00000024 ff1c4525-c036-4942-bf6c-a098755ac82f 0x00000164d106ce6b END FABE'); + msg = this.encodeGWHandshake(); + //console.log('SEND: ' + msg.toString('ascii')); + await this.sendWs(msg); + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: A-H Initialization Msg 2+3 sent'); + } + } + if (msgCounter === 1) { + if (this.protocolName === 'A:H') { + //let msg = new Buffer('MSG 0x00000362 0x0e414e46 f 0x00000001 0xf904b9f5 0x00000109 GWM MSG 0x0000b479 0x0000003b urn:tcomm-endpoint:device:deviceType:0:deviceSerialNumber:0 0x00000041 urn:tcomm-endpoint:service:serviceName:DeeWebsiteMessagingService {"command":"REGISTER_CONNECTION"}FABE'); + const msg = this.encodeGWRegisterAH(); + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Initialization Msg 4 (Register Connection) sent'); + //console.log('SEND: ' + msg.toString('ascii')); + await this.sendWs(msg); + } + + setTimeout(() => { + if (!this.websocket || this.websocket.readyState !== 1 /* OPEN */) { + return; + } + + //msg = new Buffer('4D53472030783030303030303635203078306534313465343720662030783030303030303031203078626332666262356620307830303030303036322050494E00000000D1098D8CD1098D8C000000070052006500670075006C0061007246414245', 'hex'); // "MSG 0x00000065 0x0e414e47 f 0x00000001 0xbc2fbb5f 0x00000062 PIN" + 30 + "FABE" + const msg = this.encodePing(); + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Send First Ping'); + //console.log('SEND: ' + msg.toString('hex')); + this.websocket.send(msg); + + this.pingPongInterval = setInterval(() => { + if (!this.websocket) return; + //let msg = new Buffer('4D53472030783030303030303635203078306534313465343720662030783030303030303031203078626332666262356620307830303030303036322050494E00000000D1098D8CD1098D8C000000070052006500670075006C0061007246414245', 'hex'); // "MSG 0x00000065 0x0e414e47 f 0x00000001 0xbc2fbb5f 0x00000062 PIN" + 30 + "FABE" + const msg = this.encodePing(); + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Send Ping'); + //console.log('SEND: ' + msg.toString('hex')); + this.websocket.send(msg); + + this.pongTimeout = setTimeout(() => { + this.pongTimeout = null; + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: No Pong received after 30s'); + this.websocket && this.websocket.close(); + }, 30000); + }, 180000); + }, 100); + } + msgCounter++; + if (msgCounter < 3 || !this.pingPongInterval) return; + + const incomingMsg = data.toString('ascii'); + //if (incomingMsg.includes('PON') && incomingMsg.includes('\u0000R\u0000e\u0000g\u0000u\u0000l\u0000a\u0000r')) { + if (message.service === 'FABE' && message.content && message.content.messageType === 'PON' && message.content.payloadData && message.content.payloadData.includes('\u0000R\u0000e\u0000g\u0000u\u0000l\u0000a\u0000r')) { + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Received Pong'); + if (this.initTimeout) { + clearTimeout(this.initTimeout); + this.initTimeout = null; + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Initialization completed'); + this.emit('connect'); + } + if (this.pongTimeout) { + clearTimeout(this.pongTimeout); + this.pongTimeout = null; + } + this.connectionActive = true; + return; + } + else if (message.content && message.content.payload) { + const command = message.content.payload.command; + const payload = message.content.payload.payload; + + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Command ' + command + ': ' + JSON.stringify(payload, null, 4)); + this.emit('command', command, payload); + return; + } + this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Unknown Data (' + msgCounter + '): ' + incomingMsg); + this.emit('unknown', incomingMsg); + }); + } + + encodeNumber(val, byteLen) { + if (!byteLen) byteLen = 8; + let str = val.toString(16); + while (str.length < byteLen) str = '0' + str; + return '0x' +str; + } + + generateUUID() { + const a = []; + for (let b = 0; 36 > b; b++) { + const c = 'rrrrrrrr-rrrr-4rrr-srrr-rrrrrrrrrrrr'.charAt(b); + if ('r' === c || 's' === c) { + let d = Math.floor(16 * Math.random()); + 's' === c && (d = d & 3 | 8); + a.push(d.toString(16)); + } + else a.push(c); + } + return a.join(''); + } + + encodeGWHandshake() { + //pubrelBuf = new Buffer('MSG 0x00000361 0x0e414e45 f 0x00000001 0xd7c62f29 0x0000009b INI 0x00000003 1.0 0x00000024 ff1c4525-c036-4942-bf6c-a098755ac82f 0x00000164d106ce6b END FABE'); + this.messageId++; + let msg = 'MSG 0x00000361 '; // Message-type and Channel = GW_HANDSHAKE_CHANNEL; + msg += this.encodeNumber(this.messageId) + ' f 0x00000001 '; + const idx1 = msg.length; + msg += '0x00000000 '; // Checksum! + const idx2 = msg.length; + msg += '0x0000009b '; // length content + msg += 'INI 0x00000003 1.0 0x00000024 '; // content part 1 + msg += this.generateUUID(); + msg += ' '; + msg += this.encodeNumber(Date.now(), 16); + msg += ' END FABE'; + const completeBuffer = Buffer.from(msg, 'ascii'); + + const checksum = this.computeChecksum(completeBuffer, idx1, idx2); + const checksumBuf = Buffer.from(this.encodeNumber(checksum)); + checksumBuf.copy(completeBuffer, 39); + return completeBuffer; + } + + encodeGWRegisterAH() { + //pubrelBuf = new Buffer('MSG 0x00000362 0x0e414e46 f 0x00000001 0xf904b9f5 0x00000109 GWM MSG 0x0000b479 0x0000003b urn:tcomm-endpoint:device:deviceType:0:deviceSerialNumber:0 0x00000041 urn:tcomm-endpoint:service:serviceName:DeeWebsiteMessagingService {"command":"REGISTER_CONNECTION"}FABE'); + this.messageId++; + let msg = 'MSG 0x00000362 '; // Message-type and Channel = GW_CHANNEL; + msg += this.encodeNumber(this.messageId) + ' f 0x00000001 '; + const idx1 = msg.length; + msg += '0x00000000 '; // Checksum! + const idx2 = msg.length; + msg += '0x00000109 '; // length content + msg += 'GWM MSG 0x0000b479 0x0000003b urn:tcomm-endpoint:device:deviceType:0:deviceSerialNumber:0 0x00000041 urn:tcomm-endpoint:service:serviceName:DeeWebsiteMessagingService {"command":"REGISTER_CONNECTION"}FABE'; + const completeBuffer = Buffer.from(msg, 'ascii'); + + const checksum = this.computeChecksum(completeBuffer, idx1, idx2); + const checksumBuf = Buffer.from(this.encodeNumber(checksum)); + checksumBuf.copy(completeBuffer, 39); + return completeBuffer; + } + + encodeGWRegisterAF() { + this.messageId++; + const completeBuffer = Buffer.alloc(0xe4); + completeBuffer.write('MSG', 0,'ascii'); + completeBuffer.writeUInt32BE(0x00000362, 3); // Message-type and Channel = GW_CHANNEL; + completeBuffer.writeUInt32BE(this.messageId, 7); + completeBuffer.write('f', 11, 'ascii'); + completeBuffer.writeUInt32BE(0x00000001, 12); + completeBuffer.writeUInt32BE(0x00000000, 16); // Checksum! + completeBuffer.writeUInt32BE(0x000000e4, 20); // length content + completeBuffer.write('GWM MSG 0x0000b479 0x0000003b urn:tcomm-endpoint:device:deviceType:0:deviceSerialNumber:0 0x00000041 urn:tcomm-endpoint:service:serviceName:DeeWebsiteMessagingService {"command":"REGISTER_CONNECTION"}FABE', 24, 'ascii'); + + const checksum = this.computeChecksum(completeBuffer, 16, 20); + completeBuffer.writeUInt32BE(checksum, 16); // Checksum! + return completeBuffer; + } + + encodePing() { + if (this.protocolName === 'A:H') { + return this.encodePingAH(); + } else { + return this.encodePingAF(); + } + } + + encodePingAH() { + function encode(a, b, c, h) { + a = new Uint8Array(a, c, h); + for (c = 0; c < h; c++) a[c] = b >> 8 * (h - 1 - c) & 255; + } + + this.messageId++; + let msg = 'MSG 0x00000065 '; // Message-type and Channel = CHANNEL_FOR_HEARTBEAT; + msg += this.encodeNumber(this.messageId) + ' f 0x00000001 '; + const idx1 = msg.length; + msg += '0x00000000 '; // Checksum! + const idx2 = msg.length; + msg+= '0x00000062 '; // length content + + const completeBuffer = Buffer.alloc(0x62, 0); + const startBuffer = Buffer.from(msg, 'ascii'); + startBuffer.copy(completeBuffer); + + const header = 'PIN'; + const payload = 'Regular'; // g = h.length + const n = new ArrayBuffer(header.length + 4 + 8 + 4 + 2 * payload.length); + let idx = 0; + let u = new Uint8Array(n, idx, header.length); + const l = 0; + const e = Date.now(); + + for (let q = 0; q < header.length; q++) u[q] = header.charCodeAt(q); + idx += header.length; + encode(n, l, idx, 4); + idx += 4; + encode(n, e, idx, 8); + idx += 8; + encode(n, payload.length, idx, 4); + idx += 4; + u = new Uint8Array(n, idx, payload.length * 2); + let q; + for (q = 0; q < payload.length; q++) { + u[q * 2] = 0; + u[q * 2 + 1] = payload.charCodeAt(q); + } + const buf = Buffer.from(n); + buf.copy(completeBuffer, msg.length); + + const buf2End = Buffer.from('FABE', 'ascii'); + buf2End.copy(completeBuffer, msg.length + buf.length); + + const checksum = this.computeChecksum(completeBuffer, idx1, idx2); + const checksumBuf = Buffer.from(this.encodeNumber(checksum)); + checksumBuf.copy(completeBuffer, 39); + return completeBuffer; + } + + encodePingAF() { + function encode(a, b, c, h) { + a = new Uint8Array(a, c, h); + for (c = 0; c < h; c++) a[c] = b >> 8 * (h - 1 - c) & 255; + } + + this.messageId++; + const completeBuffer = Buffer.alloc(0x3d); + completeBuffer.write('MSG', 0,'ascii'); + completeBuffer.writeUInt32BE(0x00000065, 3); // Message-type and Channel = CHANNEL_FOR_HEARTBEAT; + completeBuffer.writeUInt32BE(this.messageId, 7); + completeBuffer.write('f', 11, 'ascii'); + completeBuffer.writeUInt32BE(0x00000001, 12); + completeBuffer.writeUInt32BE(0x00000000, 16); // Checksum! + completeBuffer.writeUInt32BE(0x0000003d, 20); // length content + + const header = 'PIN'; + const payload = 'Regular'; // g = h.length + const n = new ArrayBuffer(header.length + 4 + 8 + 4 + 2 * payload.length); + let idx = 0; + let u = new Uint8Array(n, idx, header.length); + const l = 0; + const e = Date.now(); + + for (let q = 0; q < header.length; q++) u[q] = header.charCodeAt(q); + idx += header.length; + encode(n, l, idx, 4); + idx += 4; + encode(n, e, idx, 8); + idx += 8; + encode(n, payload.length, idx, 4); + idx += 4; + u = new Uint8Array(n, idx, payload.length * 2); + let q; + for (q = 0; q < payload.length; q++) { + u[q * 2] = 0; + u[q * 2 + 1] = payload.charCodeAt(q); + } + const buf = Buffer.from(n); + buf.copy(completeBuffer, 24); + + completeBuffer.write('FABE', buf.length + 24, 'ascii'); + + const checksum = this.computeChecksum(completeBuffer, 16, 20); + completeBuffer.writeUInt32BE(checksum, 16); // Checksum! + return completeBuffer; + } + + computeChecksum(a, f, k) { + function b(a, b) { + for (a = c(a); 0 != b && 0 != a;) a = Math.floor(a / 2), b--; + return a; + } + + function c(a) { + 0 > a && (a = 4294967295 + a + 1); + return a; + } + + if (k < f) throw 'Invalid checksum exclusion window!'; + a = new Uint8Array(a); + let h = 0, l = 0; + for (let e = 0; e < a.length; e++) e != f ? (l += c(a[e] << ((e & 3 ^ 3) << 3)), h += b(l, 32), l = c(l & 4294967295)) : e = k - 1; + for (; h;) l += h, h = b(l, 32), l &= 4294967295; + return c(l); + } + + parseIncomingMessage(data) { + if (this.protocolName === 'A:H') { + return this.parseIncomingMessageAH(data); + } else { + return this.parseIncomingMessageAF(data); + } + } + + parseIncomingMessageAH(data) { + function readHex(index, length) { + let str = data.toString('ascii', index, index + length); + if (str.startsWith('0x')) str = str.substr(2); + return parseInt(str, 16); + } + + function readString(index, length) { + return data.toString('ascii', index, index + length); + } + + let idx = 0; + const message = {}; + message.service = readString(data.length - 4, 4); + + if (message.service === 'TUNE') { + message.checksum = readHex(idx, 10); + idx += 11; // 10 + delimiter; + const contentLength = readHex(idx, 10); + idx += 11; // 10 + delimiter; + message.content = readString(idx, contentLength - 4 - idx); + if (message.content.startsWith('{') && message.content.endsWith('}')) { + try { + message.content = JSON.parse(message.content); + } + catch (e) { + // ignore + } + } + } + else if (message.service === 'FABE') { + message.messageType = readString(idx, 3); + idx += 4; + message.channel = readHex(idx, 10); + idx += 11; // 10 + delimiter; + message.messageId = readHex(idx, 10); + idx += 11; // 10 + delimiter; + message.moreFlag = readString(idx, 1); + idx += 2; // 1 + delimiter; + message.seq = readHex(idx, 10); + idx += 11; // 10 + delimiter; + message.checksum = readHex(idx, 10); + idx += 11; // 10 + delimiter; + + //const contentLength = readHex(idx, 10); + idx += 11; // 10 + delimiter; + + message.content = {}; + message.content.messageType = readString(idx, 3); + idx += 4; + + if (message.channel === 0x361) { // GW_HANDSHAKE_CHANNEL + if (message.content.messageType === 'ACK') { + let length = readHex(idx, 10); + idx += 11; // 10 + delimiter; + message.content.protocolVersion = readString(idx, length); + idx += length + 1; + length = readHex(idx, 10); + idx += 11; // 10 + delimiter; + message.content.connectionUUID = readString(idx, length); + idx += length + 1; + message.content.established = readHex(idx, 10); + idx += 11; // 10 + delimiter; + message.content.timestampINI = readHex(idx, 18); + idx += 19; // 18 + delimiter; + message.content.timestampACK = readHex(idx, 18); + idx += 19; // 18 + delimiter; + } + } + else if (message.channel === 0x362) { // GW_CHANNEL + if (message.content.messageType === 'GWM') { + message.content.subMessageType = readString(idx, 3); + idx += 4; + message.content.channel = readHex(idx, 10); + idx += 11; // 10 + delimiter; + + if (message.content.channel === 0xb479) { // DEE_WEBSITE_MESSAGING + let length = readHex(idx, 10); + idx += 11; // 10 + delimiter; + message.content.destinationIdentityUrn = readString(idx, length); + idx += length + 1; + + length = readHex(idx, 10); + idx += 11; // 10 + delimiter; + let idData = readString(idx, length); + idx += length + 1; + + idData = idData.split(' '); + message.content.deviceIdentityUrn = idData[0]; + message.content.payload = idData[1]; + if (!message.content.payload) { + message.content.payload = readString(idx, data.length - 4 - idx); + } + if (message.content.payload.startsWith('{') && message.content.payload.endsWith('}')) { + try { + message.content.payload = JSON.parse(message.content.payload); + if (message.content.payload && message.content.payload.payload && typeof message.content.payload.payload === 'string') { + message.content.payload.payload = JSON.parse(message.content.payload.payload); + } + } + catch (e) { + // Ignore + } + } + } + } + } + else if (message.channel === 0x65) { // CHANNEL_FOR_HEARTBEAT + idx -= 1; // no delimiter! + message.content.payloadData = data.slice(idx, data.length - 4); + } + } + //console.log(JSON.stringify(message, null, 4)); + return message; + } + + parseIncomingMessageAF(data) { + function readHex(index, length) { + let str = data.toString('ascii', index, index + length); + if (str.startsWith('0x')) str = str.substr(2); + return parseInt(str, 16); + } + + function readString(index, length) { + return data.toString('ascii', index, index + length); + } + + let idx = 0; + const message = {}; + message.service = readString(data.length - 4, 4); + + //this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Incoming message Service: ' + message.service); + + if (message.service === 'TUNE') { + message.checksum = readHex(idx, 10); + idx += 11; // 10 + delimiter; + const contentLength = readHex(idx, 10); + idx += 11; // 10 + delimiter; + message.content = readString(idx, contentLength - 4 - idx); + if (message.content.startsWith('{') && message.content.endsWith('}')) { + try { + message.content = JSON.parse(message.content); + } + catch (e) { + // Ignore + } + } + } + else if (message.service === 'FABE') { + message.messageType = readString(0, 3); + message.channel = data.readUInt32BE(3); + message.messageId = data.readUInt32BE(7); + message.moreFlag = data.readUInt32BE(11); + message.seq = data.readUInt32BE(12); + message.checksum = data.readUInt32BE(16); + + //const contentLength = data.readUInt32BE(20); + + message.content = {}; + message.content.messageType = readString(24, 3); + idx = 28; + + //this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Incoming message Service Channel: ' + message.channel); + + if (message.channel === 0x361) { // GW_HANDSHAKE_CHANNEL + if (message.content.messageType === 'ACK') { + let length = readHex(idx, 10); + idx += 11; // 10 + delimiter; + message.content.protocolVersion = readString(idx, length); + idx += length + 1; + length = readHex(idx, 10); + idx += 11; // 10 + delimiter; + message.content.connectionUUID = readString(idx, length); + idx += length + 1; + message.content.established = readHex(idx, 10); + idx += 11; // 10 + delimiter; + message.content.timestampINI = readHex(idx, 18); + idx += 19; // 18 + delimiter; + message.content.timestampACK = readHex(idx, 18); + idx += 19; // 18 + delimiter; + } + } + else if (message.channel === 0x362) { // GW_CHANNEL + if (message.content.messageType === 'GWM') { + message.content.subMessageType = readString(idx, 3); + idx += 4; + message.content.channel = readHex(idx, 10); + idx += 11; // 10 + delimiter; + + if (message.content.channel === 0xb479) { // DEE_WEBSITE_MESSAGING + let length = readHex(idx, 10); + idx += 11; // 10 + delimiter; + message.content.destinationIdentityUrn = readString(idx, length); + idx += length + 1; + + length = readHex(idx, 10); + idx += 11; // 10 + delimiter; + let idData = readString(idx, length); + idx += length + 1; + + idData = idData.split(' '); + message.content.deviceIdentityUrn = idData[0]; + message.content.payload = idData[1]; + if (!message.content.payload) { + message.content.payload = readString(idx, data.length - 4 - idx); + } + if (message.content.payload.startsWith('{') && message.content.payload.endsWith('}')) { + try { + message.content.payload = JSON.parse(message.content.payload); + if (message.content.payload && message.content.payload.payload && typeof message.content.payload.payload === 'string') { + message.content.payload.payload = JSON.parse(message.content.payload.payload); + } + } + catch (e) { + // Ignore + } + } + } + } + } + else if (message.channel === 0x65) { // CHANNEL_FOR_HEARTBEAT + message.content.payloadData = data.slice(idx, data.length - 4); + } + } + //console.log(JSON.stringify(message, null, 4)); + //this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Parsed Message: ' + JSON.stringify(message)); + return message; + } + + disconnect() { + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = null; + } + if (this.pollingTimeout) { + clearTimeout(this.pollingTimeout); + this.pollingTimeout = null; + } + if (this.initTimeout) { + clearTimeout(this.initTimeout); + this.initTimeout = null; + } + this.stop = true; + if (!this.websocket) return; + try { + this.websocket.close(); + } catch (e) { + this.connectionActive && this._options.logger && this._options.logger('Alexa-Remote WS-MQTT: Disconnect error: ' + e.message); + } + } +} + + +module.exports = AlexaWsMqtt; diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 69a4aea7..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,33 +0,0 @@ -version: 'test-{build}' - -# Test against this version of Node.js -environment: - matrix: - - nodejs_version: "0.10" - - nodejs_version: "0.12" - - nodejs_version: "4" - - nodejs_version: "6" - -platform: - - x86 - - x64 - -clone_folder: c:\projects\%APPVEYOR_PROJECT_NAME% -# Install scripts. (runs after repo cloning) -install: - # Get the latest stable version of Node.js or io.js - - ps: Install-Product node $env:nodejs_version $env:platform - # install modules - - npm install - -# Post-install test scripts. -test_script: - # Output useful info for debugging. - - echo %cd% - - node --version - - npm --version - # run tests - - npm test - -# Don't actually build. -build: off diff --git a/example/example.js b/example/example.js new file mode 100644 index 00000000..4e644fdb --- /dev/null +++ b/example/example.js @@ -0,0 +1,64 @@ +let Alexa = require('../alexa-remote'); +let alexa = new Alexa(); + +/***************************************************************/ +// see: https://www.gehrig.info/alexa/Alexa.html +// cookie starts with x-amzn-dat and ends with =" csrf=12345780 +let cookie = { ... }; + +alexa.on('cookie', (cookie, csrf, macDms) => { + // This event is triggered when a cookie is generated OR refreshed. + // Store these values for subsequent starts + // + // cookie to provide in options.cookie on next start + // csrf belongs to cookie and both needed, but is normally extracted from cookie again on start + // macDms needed for push connections. Provide in options.macDms on next start + // Get alexa.cookieData and store too and provide the content in options.formerRegistrationData on next start to allow cookie refreshs + // You can also just store alexa.cookieData and provide this object as options.cookie, then options.formerRegistrationData is handled internally too +}); + + +alexa.init({ + cookie: cookie, // cookie if already known, else can be generated using proxy + proxyOnly: true, + proxyOwnIp: 'localhost', // required if proxy enabled: provide the own IP with which you later access the proxy. + // Providing/Using a hostname here can lead to issues! + proxyPort: 3001, // optional: use this port for the proxy + proxyLogLevel: 'info', + bluetooth: true, + logger: console.log, // optional + + // The following options are optional. Try without them first and just use really needed ones!! + + alexaServiceHost: 'layla.amazon.de', // optional, e.g. "pitangui.amazon.com" for amazon.com, default is "layla.amazon.de" +// userAgent: '...', // optional, override used user-Agent for all Requests and Cookie determination +// acceptLanguage: '...', // optional, override Accept-Language-Header for cookie determination +// amazonPage: '...', // optional, override Amazon-Login-Page for cookie determination and referer for requests + useWsMqtt: true, // optional, true to use the Websocket/MQTT direct push connection +// pushDispatchHost: '...', // optional, override push endpoint host + cookieRefreshInterval: 7*24*60*1000, // optional, cookie refresh intervall, set to 0 to disable refresh + deviceAppName: '...', // optional: name of the device app name which will be registered with Amazon, leave empty to use a default one + apiUserAgentPostFix: '...', // optional: postfix to add to api useragent, leave empty to use a default one + formerDataStorePath: '...', // optional: overwrite path where some of the formerRegistrationData are persisted to optimize against Amazon security measures + formerRegistrationData: { ... }, // optional/preferred: provide the result object from subsequent proxy usages (cookieData) here and some generated data will be reused for next proxy call too + macDms: { ... }, // required in version 4.0 to use old Push connection! Is returned in cookieData.macDms, No longer needed since HTTP2-Push connection + usePushConnectType: 3, // define which push connect type is used to initialize the push connection (type 1 and 2 or outdated, defaults to 3 + autoQueryActivityOnTrigger: false, // optional: if true, alexa will query the activity on each - use with care and only if needed! + }, + function (err) { + if (err) { + console.log (err); + return; + } + for (let deviceSerial of Object.keys(alexa.serialNumbers)) { + console.log(deviceSerial); + + // const device = alexa.find(deviceSerial); // find device object by serial number + // console.log(JSON.stringify(device, null, 2)); // print device object + + // device.sendCommand('volume', 50); // some methods are exposed on device object and can be called without device/deviceSerial + // alexa.sendCommand(device, 'volume', 50); // but can also be called by providing the device object or serial on main class instance + // alexa.sendSequenceCommand(deviceSerial, 'speak', 'Hello friends'); // send sequence command to device + } + } +); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..61af611a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5070 @@ +{ + "name": "alexa-remote2", + "version": "8.0.4", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "alexa-remote2", + "version": "8.0.4", + "license": "MIT", + "dependencies": { + "alexa-cookie2": "^5.0.3", + "extend": "^3.0.2", + "https": "^1.0.0", + "querystring": "^0.2.1", + "uuid": "^11.1.0", + "ws": "^8.18.3" + }, + "devDependencies": { + "@alcalzone/release-script": "^5.0.0", + "@alcalzone/release-script-plugin-license": "^4.0.0", + "eslint": "^8.57.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@alcalzone/pak": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@alcalzone/pak/-/pak-0.11.0.tgz", + "integrity": "sha512-S6s2Xug8VJ04Xgam7kV+dUydVB2gJmTem+Kr7oxneeXndWddgoQxphQNI9WqgpsifTkonC9wiAbj3qkMFlNeAA==", + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script/-/release-script-5.0.0.tgz", + "integrity": "sha512-BKkD5wQBg/EF5lhAGkF9mgWFLLP+59RBoEVyvFOB1IRKOQOmwo8OYe7M0Q23yMCOpjzY9RU1CC2ESff7Mq/w8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/release-script-core": "4.0.0", + "@alcalzone/release-script-plugin-changelog": "4.0.0", + "@alcalzone/release-script-plugin-exec": "4.0.0", + "@alcalzone/release-script-plugin-git": "5.0.0", + "@alcalzone/release-script-plugin-package": "4.0.0", + "@alcalzone/release-script-plugin-version": "4.0.0", + "alcalzone-shared": "^5.0.0", + "axios": "^1.6.2", + "enquirer": "^2.3.6", + "fs-extra": "^10.1.0", + "picocolors": "1.0.0", + "semver": "^7.7.2", + "source-map-support": "^0.5.21", + "yargs": "^17.4.1" + }, + "bin": { + "release-script": "bin/release.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@alcalzone/release-script-core": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-core/-/release-script-core-4.0.0.tgz", + "integrity": "sha512-vwBhNzsUBpGDCVrL0fAkOkdsxCaGOC43UOgTbB4U27noBgz+J9rscc53DiafKniSyfK6+NgWzHcjwY1vtyvaEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@alcalzone/release-script-plugin-changelog": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-changelog/-/release-script-plugin-changelog-4.0.0.tgz", + "integrity": "sha512-jFC8TCTWhFr0dkgFkKRKuEqCTvNqqpTAullbXbOK33oOyBHvsk8M6ma8fOFx3caH1WLQCWwmzt+nr7+Pwx3AqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/release-script-core": "4.0.0", + "alcalzone-shared": "^5.0.0", + "fs-extra": "^10.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@alcalzone/release-script-plugin-exec": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-exec/-/release-script-plugin-exec-4.0.0.tgz", + "integrity": "sha512-STEQrbI91p+ZklKrSQq9obJ/OWMNC8U9GGYyy0Z36gMkfBTWYPu6B9aF+dMCBRdHvvacrvvMaux/uirt9EH3tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/release-script-core": "4.0.0", + "alcalzone-shared": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@alcalzone/release-script-plugin-git": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-git/-/release-script-plugin-git-5.0.0.tgz", + "integrity": "sha512-OigoFIHQsZm6koYvFycnOolvGTjqh4XaL2P4E9IGSL8imIvuZz4FZ0X4bg3lV5wRY9AUYo1ui4lGfvcgorD+lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/release-script-core": "4.0.0", + "fs-extra": "^10.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@alcalzone/release-script-plugin-license": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-license/-/release-script-plugin-license-4.0.0.tgz", + "integrity": "sha512-j9akvL9pvPE+KZdQmjF7/zQv2GoFESBSwrxnlIAaFVxuP0ApdnhnmRAMEsyJsLAFT5kpGpHVnjJ8K5dPKEvL+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/release-script-core": "4.0.0", + "fs-extra": "^10.1.0", + "tiny-glob": "^0.2.9" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@alcalzone/release-script-plugin-package": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-package/-/release-script-plugin-package-4.0.0.tgz", + "integrity": "sha512-Abxl9Ix/UtAvQTW3dzxHvl7L1ONigxV924KH6HIeqsOe/m2geeKTekbMxz76+EkBDusckKHWUq9pQWKOPv2ZNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/pak": "^0.11.0", + "@alcalzone/release-script-core": "4.0.0", + "alcalzone-shared": "^5.0.0", + "fs-extra": "^10.1.0", + "semver": "^7.7.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@alcalzone/release-script-plugin-version": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-version/-/release-script-plugin-version-4.0.0.tgz", + "integrity": "sha512-ZbgGum7M1X0paDFYtcrlZRptPXy4SPqMDAZU6JgU/JSmifBSvuwEJpF42vt5POICrz3FkEmNUUdp9pZ44gYFXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/release-script-core": "4.0.0", + "alcalzone-shared": "^5.0.0", + "fs-extra": "^10.1.0", + "semver": "^7.7.2", + "tiny-glob": "^0.2.9" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", + "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/http-proxy": { + "version": "1.17.16", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", + "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "24.0.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.13.tgz", + "integrity": "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/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/alexa-cookie2": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/alexa-cookie2/-/alexa-cookie2-5.0.3.tgz", + "integrity": "sha512-z9G32J0+mDgtR/8+3lF49h+yKU5mlX1NwDPR1A67UjX1TjHhK8m552uI/0E4TpL9/6c3VbmNWmbkKRm8imSsRg==", + "license": "MIT", + "dependencies": { + "cookie": "^0.6.0", + "express": "^4.21.2", + "http-proxy-middleware": "^2.0.9", + "http-proxy-response-rewrite": "^0.0.1", + "https": "^1.0.0", + "querystring": "^0.2.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/alexa-cookie2/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "dev": true, + "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 + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "node_modules/bufferhelper": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/bufferhelper/-/bufferhelper-0.2.1.tgz", + "integrity": "sha1-+nSjhXJKWOJC8ErWZGwjZvg7kT4=", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=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, + "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/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/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/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "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==", + "dev": true, + "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/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "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/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.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/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==", + "dev": true, + "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/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.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, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "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/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.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/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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 + }, + "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 + }, + "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.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.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==", + "dev": true, + "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/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/http-proxy-response-rewrite": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-response-rewrite/-/http-proxy-response-rewrite-0.0.1.tgz", + "integrity": "sha512-smtaa2sKgiWrP9c9W+/MFzgjeh3A4zsQOLh1S3rp1NsmNYIVO07AlWUnhoUnMZIuxY6+3v7OS5NlDGX2I2DWBQ==", + "dependencies": { + "bufferhelper": "^0.2.1", + "concat-stream": "^1.5.1" + } + }, + "node_modules/https": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz", + "integrity": "sha1-PDfHrhqO65ZpBKKtHpdaGUt+06Q=" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/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/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "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==", + "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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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/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/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "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 + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystring": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", + "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.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/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/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, + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "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" + ], + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "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, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, + "@alcalzone/pak": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@alcalzone/pak/-/pak-0.11.0.tgz", + "integrity": "sha512-S6s2Xug8VJ04Xgam7kV+dUydVB2gJmTem+Kr7oxneeXndWddgoQxphQNI9WqgpsifTkonC9wiAbj3qkMFlNeAA==", + "dev": true, + "requires": { + "axios": "^1.6.2", + "execa": "~5.0.1", + "fs-extra": "^10.1.0", + "semver": "^7.3.7", + "tiny-glob": "^0.2.9" + }, + "dependencies": { + "execa": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.1.tgz", + "integrity": "sha512-4hFTjFbFzQa3aCLobpbPJR/U+VoL1wdV5ozOWjeet0AWDeYr9UFGM1eUFWHX+VtOWFq4p0xXUXfW1YxUaP4fpw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + } + } + }, + "@alcalzone/release-script": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script/-/release-script-5.0.0.tgz", + "integrity": "sha512-BKkD5wQBg/EF5lhAGkF9mgWFLLP+59RBoEVyvFOB1IRKOQOmwo8OYe7M0Q23yMCOpjzY9RU1CC2ESff7Mq/w8w==", + "dev": true, + "requires": { + "@alcalzone/release-script-core": "4.0.0", + "@alcalzone/release-script-plugin-changelog": "4.0.0", + "@alcalzone/release-script-plugin-exec": "4.0.0", + "@alcalzone/release-script-plugin-git": "5.0.0", + "@alcalzone/release-script-plugin-package": "4.0.0", + "@alcalzone/release-script-plugin-version": "4.0.0", + "alcalzone-shared": "^5.0.0", + "axios": "^1.6.2", + "enquirer": "^2.3.6", + "fs-extra": "^10.1.0", + "picocolors": "1.0.0", + "semver": "^7.7.2", + "source-map-support": "^0.5.21", + "yargs": "^17.4.1" + } + }, + "@alcalzone/release-script-core": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-core/-/release-script-core-4.0.0.tgz", + "integrity": "sha512-vwBhNzsUBpGDCVrL0fAkOkdsxCaGOC43UOgTbB4U27noBgz+J9rscc53DiafKniSyfK6+NgWzHcjwY1vtyvaEQ==", + "dev": true, + "requires": { + "execa": "^5.1.1" + } + }, + "@alcalzone/release-script-plugin-changelog": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-changelog/-/release-script-plugin-changelog-4.0.0.tgz", + "integrity": "sha512-jFC8TCTWhFr0dkgFkKRKuEqCTvNqqpTAullbXbOK33oOyBHvsk8M6ma8fOFx3caH1WLQCWwmzt+nr7+Pwx3AqQ==", + "dev": true, + "requires": { + "@alcalzone/release-script-core": "4.0.0", + "alcalzone-shared": "^5.0.0", + "fs-extra": "^10.1.0" + } + }, + "@alcalzone/release-script-plugin-exec": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-exec/-/release-script-plugin-exec-4.0.0.tgz", + "integrity": "sha512-STEQrbI91p+ZklKrSQq9obJ/OWMNC8U9GGYyy0Z36gMkfBTWYPu6B9aF+dMCBRdHvvacrvvMaux/uirt9EH3tQ==", + "dev": true, + "requires": { + "@alcalzone/release-script-core": "4.0.0", + "alcalzone-shared": "^5.0.0" + } + }, + "@alcalzone/release-script-plugin-git": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-git/-/release-script-plugin-git-5.0.0.tgz", + "integrity": "sha512-OigoFIHQsZm6koYvFycnOolvGTjqh4XaL2P4E9IGSL8imIvuZz4FZ0X4bg3lV5wRY9AUYo1ui4lGfvcgorD+lw==", + "dev": true, + "requires": { + "@alcalzone/release-script-core": "4.0.0", + "fs-extra": "^10.1.0" + } + }, + "@alcalzone/release-script-plugin-license": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-license/-/release-script-plugin-license-4.0.0.tgz", + "integrity": "sha512-j9akvL9pvPE+KZdQmjF7/zQv2GoFESBSwrxnlIAaFVxuP0ApdnhnmRAMEsyJsLAFT5kpGpHVnjJ8K5dPKEvL+A==", + "dev": true, + "requires": { + "@alcalzone/release-script-core": "4.0.0", + "fs-extra": "^10.1.0", + "tiny-glob": "^0.2.9" + } + }, + "@alcalzone/release-script-plugin-package": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-package/-/release-script-plugin-package-4.0.0.tgz", + "integrity": "sha512-Abxl9Ix/UtAvQTW3dzxHvl7L1ONigxV924KH6HIeqsOe/m2geeKTekbMxz76+EkBDusckKHWUq9pQWKOPv2ZNQ==", + "dev": true, + "requires": { + "@alcalzone/pak": "^0.11.0", + "@alcalzone/release-script-core": "4.0.0", + "alcalzone-shared": "^5.0.0", + "fs-extra": "^10.1.0", + "semver": "^7.7.2" + } + }, + "@alcalzone/release-script-plugin-version": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-version/-/release-script-plugin-version-4.0.0.tgz", + "integrity": "sha512-ZbgGum7M1X0paDFYtcrlZRptPXy4SPqMDAZU6JgU/JSmifBSvuwEJpF42vt5POICrz3FkEmNUUdp9pZ44gYFXQ==", + "dev": true, + "requires": { + "@alcalzone/release-script-core": "4.0.0", + "alcalzone-shared": "^5.0.0", + "fs-extra": "^10.1.0", + "semver": "^7.7.2", + "tiny-glob": "^0.2.9" + } + }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", + "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.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" + } + }, + "@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true + }, + "@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@types/http-proxy": { + "version": "1.17.16", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", + "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "24.0.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.13.tgz", + "integrity": "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ==", + "requires": { + "undici-types": "~7.8.0" + } + }, + "@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "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 + }, + "alexa-cookie2": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/alexa-cookie2/-/alexa-cookie2-5.0.3.tgz", + "integrity": "sha512-z9G32J0+mDgtR/8+3lF49h+yKU5mlX1NwDPR1A67UjX1TjHhK8m552uI/0E4TpL9/6c3VbmNWmbkKRm8imSsRg==", + "requires": { + "cookie": "^0.6.0", + "express": "^4.21.2", + "http-proxy-middleware": "^2.0.9", + "http-proxy-response-rewrite": "^0.0.1", + "https": "^1.0.0", + "querystring": "^0.2.1" + }, + "dependencies": { + "cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" + } + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "dev": true, + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "requires": { + "fill-range": "^7.1.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "bufferhelper": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/bufferhelper/-/bufferhelper-0.2.1.tgz", + "integrity": "sha1-+nSjhXJKWOJC8ErWZGwjZvg7kT4=" + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "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==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "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 + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "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==" + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "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==", + "requires": { + "es-errors": "^1.3.0" + } + }, + "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==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + } + }, + "eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "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 + }, + "espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "requires": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + } + }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" + }, + "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==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "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, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "requires": { + "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" + } + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "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 + }, + "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 + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.3" + } + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "requires": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + } + }, + "http-proxy-response-rewrite": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-response-rewrite/-/http-proxy-response-rewrite-0.0.1.tgz", + "integrity": "sha512-smtaa2sKgiWrP9c9W+/MFzgjeh3A4zsQOLh1S3rp1NsmNYIVO07AlWUnhoUnMZIuxY6+3v7OS5NlDGX2I2DWBQ==", + "requires": { + "bufferhelper": "^0.2.1", + "concat-stream": "^1.5.1" + } + }, + "https": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz", + "integrity": "sha1-PDfHrhqO65ZpBKKtHpdaGUt+06Q=" + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "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 + }, + "is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==" + }, + "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 + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash.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 + }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" + }, + "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 + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "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 + }, + "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==" + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "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==", + "dev": true + }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true + }, + "qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "requires": { + "side-channel": "^1.0.6" + } + }, + "querystring": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", + "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==" + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "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 + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true + }, + "send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "requires": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "requires": { + "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" + } + }, + "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==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "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==", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "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==", + "requires": { + "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" + } + }, + "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 + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "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, + "requires": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==" + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "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==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "requires": {} + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + }, + "yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 513a8b15..b71e9fa0 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,26 @@ { - "name": "alexa-remote", - "version": "0.0.24", + "name": "alexa-remote2", + "version": "8.0.4", "description": "Remote Control for amazon echo devices", "author": { - "name": "soef", - "email": "soef@gmx.net" + "name": "Apollon77", + "email": "iobroker@fischer-ka.de" }, "contributors": [ { "name": "soef", "email": "soef@gmx.net" + }, + { + "name": "Apollon77", + "email": "iobroker@fischer-ka.de" + }, + { + "name": "bbindreiter", + "email": "bernd.bindreiter@gmail.com" } ], - "homepage": "https://github.com/soef/alexa-remote", + "homepage": "https://github.com/Apollon77/alexa-remote", "license": "MIT", "keywords": [ "alexa", @@ -21,23 +29,33 @@ "layla.amazon.de" ], "dependencies": { - "alexa-cookie": "^0.0.20", - "https": "^1.0.0" + "alexa-cookie2": "^5.0.3", + "https": "^1.0.0", + "querystring": "^0.2.1", + "ws": "^8.18.3", + "extend": "^3.0.2", + "uuid": "^11.1.0" }, "devDependencies": { - "mocha": "^2.3.4", - "chai": "^3.4.1" + "@alcalzone/release-script": "^5.0.0", + "@alcalzone/release-script-plugin-license": "^4.0.0", + "eslint": "^8.57.1" }, "scripts": { - "test": "node node_modules/mocha/bin/mocha" + "test": "node node_modules/mocha/bin/mocha", + "release": "release-script" }, "repository": { "type": "git", - "url": "git+https://github.com/soef/alexa-remote.git" + "url": "git+https://github.com/Apollon77/alexa-remote.git" }, "bugs": { - "url": "https://github.com/soef/alexa-remote/issues" + "url": "https://github.com/Apollon77/alexa-remote/issues" + }, + "engines": { + "node": ">=16.0.0" }, + "types": "alexa-remote.d.ts", "main": "alexa-remote.js", "readmeFilename": "readme.md" } diff --git a/readme.md b/readme.md deleted file mode 100644 index b22e3c70..00000000 --- a/readme.md +++ /dev/null @@ -1,34 +0,0 @@ - -## alexa-remote - -Library to remote control an Alexa (Amazon Echo) device via LAN/WLAN. - -Early code version. - - - - -```js -let Alexa = require('alexa-remote'); -let alexa = new Alexa(); - -/***************************************************************/ -// see: https://www.gehrig.info/alexa/Alexa.html -// cookie starts with x-amzn-dat and ends with =" csrf=12345780 -let cookie = 'x-amzn-dat.../ /...=" csrf=12345780'; - -alexa.init({ - cookie: cookie, - bluetooth: true - }, - function () { - for (let device of this.devices) { - console.log (device._name); - } - } -); -```` \ No newline at end of file