From ca1f169fd7e6f99d8424054bcaaeca793359627c Mon Sep 17 00:00:00 2001 From: Donavan Becker Date: Tue, 30 Jan 2024 23:48:42 -0600 Subject: [PATCH] v2.0.0 (#810) ## [2.0.0](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v2.0.0) (2024-01-31) ### What's Changes - Moved from CommonJS to ES Module - Housekeeping and updated dependencies. **Full Changelog**: https://github.com/donavanbecker/homebridge-resideo/compare/v1.4.6...v2.0.0 --- .github/auto-merge.yml | 3 + .github/release-drafter.yml | 33 + .github/workflows/changerelease.yml | 13 + .github/workflows/dependabot.yml | 18 +- .github/workflows/discord-webhooks.yml | 17 + .github/workflows/release-drafter.yml | 10 +- .vscode/settings.json | 9 +- CHANGELOG.md | 34 +- LICENSE | 190 +- config.schema.json | 19 +- homebridge-ui/public/index.html | 2 +- homebridge-ui/server.js | 9 +- package-lock.json | 3087 ++++++++++++++++++++++-- package.json | 36 +- src/devices/leaksensors.ts | 315 +-- src/devices/roomsensors.ts | 263 +- src/devices/roomsensorthermostats.ts | 497 ++-- src/devices/thermostats.ts | 719 +++--- src/devices/valve.ts | 322 ++- src/homebridge-ui/public/index.html | 442 ++++ src/homebridge-ui/server.ts | 161 ++ src/index.ts | 17 +- src/platform.ts | 581 ++--- src/settings.ts | 63 +- tsconfig.json | 28 +- 25 files changed, 5221 insertions(+), 1667 deletions(-) create mode 100644 .github/auto-merge.yml create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/changerelease.yml create mode 100644 .github/workflows/discord-webhooks.yml create mode 100644 src/homebridge-ui/public/index.html create mode 100644 src/homebridge-ui/server.ts diff --git a/.github/auto-merge.yml b/.github/auto-merge.yml new file mode 100644 index 00000000..9a6f2f89 --- /dev/null +++ b/.github/auto-merge.yml @@ -0,0 +1,3 @@ +- match: + dependency_type: all + update_type: all \ No newline at end of file diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000..0e1da2a2 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,33 @@ +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' + +categories: + - title: 'Workflow Changes' + labels: + - 'workflow' + - title: 'Enhancements' + labels: + - 'enhancement' + - title: 'Updated Dependencies' + labels: + - 'dependencies' + - title: 'Documentation' + labels: + - 'docs' + +change-template: '- $TITLE @$AUTHOR [#$NUMBER]' +version-resolver: + major: + labels: + - 'major' + minor: + labels: + - 'minor' + patch: + labels: + - 'patch' + default: patch +template: | + ## Changes + + $CHANGES \ No newline at end of file diff --git a/.github/workflows/changerelease.yml b/.github/workflows/changerelease.yml new file mode 100644 index 00000000..e66082d3 --- /dev/null +++ b/.github/workflows/changerelease.yml @@ -0,0 +1,13 @@ +name: Changelog to Release + +on: + workflow_dispatch: + push: + paths: [CHANGELOG.md] + branches: [latest] + +jobs: + changerelease: + uses: donavanbecker/.github/.github/workflows/changerelease.yml@latest + secrets: + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml index 36f19d83..568711a5 100644 --- a/.github/workflows/dependabot.yml +++ b/.github/workflows/dependabot.yml @@ -2,16 +2,16 @@ name: AutoDependabot on: pull_request: - push: branches: - beta + - latest + pull_request_target: + branches: + - beta + - latest jobs: - automerge: - name: Auto-merge dependabot updates - runs-on: ubuntu-latest - steps: - - uses: mitto98/dependabot-automerge-action@master - with: - token: ${{ github.token }} - merge: true + label: + uses: donavanbecker/.github/.github/workflows/dependabot.yml@latest + secrets: + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/discord-webhooks.yml b/.github/workflows/discord-webhooks.yml new file mode 100644 index 00000000..7037ba9c --- /dev/null +++ b/.github/workflows/discord-webhooks.yml @@ -0,0 +1,17 @@ +# This is a basic workflow to help you get started with Actions + +name: Discord Webhooks + +# Controls when the workflow will run +on: + release: + types: [released, prereleased] + +jobs: + github-releases-to-discord: + uses: donavanbecker/.github/.github/workflows/discord-webhooks.yml@latest + with: + footer_title: "Meater" + secrets: + DISCORD_WEBHOOK_URL_LATEST: ${{ secrets.DISCORD_WEBHOOK_URL_LATEST }} + DISCORD_WEBHOOK_URL_BETA: ${{ secrets.DISCORD_WEBHOOK_URL_BETA }} \ No newline at end of file diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index de43b122..7c630a40 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -2,13 +2,11 @@ name: Release Drafter on: push: - branches: [latest] - pull_request: # required for autolabeler - types: [opened, reopened, synchronize] - workflow_dispatch: + branches: + - latest jobs: - stale: + release-drafter: uses: donavanbecker/.github/.github/workflows/release-drafter.yml@latest secrets: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 7073c148..82c47126 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,10 +6,10 @@ }, "editor.defaultFormatter": "esbenp.prettier-vscode", "[javascript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "vscode.typescript-language-features" }, "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "vscode.typescript-language-features" }, "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" @@ -23,6 +23,7 @@ } ], "[json]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - } + "editor.defaultFormatter": "vscode.json-language-features" + }, + "codeQL.githubDatabase.update": "never" } diff --git a/CHANGELOG.md b/CHANGELOG.md index a2677bb8..65dc97e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,42 +2,50 @@ All notable changes to this project will be documented in this file. This project uses [Semantic Versioning](https://semver.org/) -## [Version 1.4.6](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.4.6) (2024-01-16) +## [2.0.0](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v2.0.0) (2024-01-31) -## What's Changes +### What's Changes +- Moved from CommonJS to ES Module +- Housekeeping and updated dependencies. + +**Full Changelog**: https://github.com/donavanbecker/homebridge-resideo/compare/v1.4.6...v2.0.0 + +## [1.4.6](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.4.6) (2024-01-16) + +### What's Changes - Housekeeping and updated dependencies. **Full Changelog**: https://github.com/donavanbecker/homebridge-resideo/compare/v1.4.5...v1.4.6 -## [Version 1.4.5](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.4.5) (2023-12-15) +## [1.4.5](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.4.5) (2023-12-15) -## What's Changes +### What's Changes - Housekeeping and updated dependencies. **Full Changelog**: https://github.com/donavanbecker/homebridge-resideo/compare/v1.4.4...v1.4.5 -## [Version 1.4.4](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.4.4) (2023-11-26) +## [1.4.4](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.4.4) (2023-11-26) ## What's Changes - Housekeeping and updated dependencies. **Full Changelog**: https://github.com/donavanbecker/homebridge-resideo/compare/v1.4.3...v1.4.4 -## [Version 1.4.3](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.4.3) (2023-10-31) +## [1.4.3](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.4.3) (2023-10-31) ## What's Changes - Housekeeping and updated dependencies. **Full Changelog**: https://github.com/donavanbecker/homebridge-resideo/compare/v1.4.2...v1.4.3 -## [Version 1.4.2](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.4.2) (2023-08-27) +## [1.4.2](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.4.2) (2023-08-27) ## What's Changes - Housekeeping and updated dependencies. **Full Changelog**: https://github.com/donavanbecker/homebridge-resideo/compare/v1.4.1...v1.4.2 -## [Version 1.4.1](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.4.1) (2023-08-19) +## [1.4.1](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.4.1) (2023-08-19) ## What's Changes - Fixed LeakSensor Inital status not pulling on restart. @@ -45,7 +53,7 @@ All notable changes to this project will be documented in this file. This projec **Full Changelog**: https://github.com/donavanbecker/homebridge-resideo/compare/v1.4.0...v1.4.1 -## [Version 1.4.0](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.4.0) (2023-08-19) +## [1.4.0](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.4.0) (2023-08-19) ## What's Changes - Added Support for reading status of L5 Water Shutoff. @@ -53,7 +61,7 @@ All notable changes to this project will be documented in this file. This projec **Full Changelog**: https://github.com/donavanbecker/homebridge-resideo/compare/v1.3.0...v1.4.0 -## [Version 1.3.0](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.3.0) (2023-04-08) +## [1.3.0](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.3.0) (2023-04-08) ## What's Changes - Added Config that allows `Auto` mode to be enabled for Thermostats even if API doesn't have `Auto` enabled. @@ -62,7 +70,7 @@ All notable changes to this project will be documented in this file. This projec **Full Changelog**: https://github.com/donavanbecker/homebridge-resideo/compare/v1.2.0...v1.3.0 -## [Version 1.2.0](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.2.0) (2022-12-08) +## [1.2.0](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.2.0) (2022-12-08) ## What's Changes - Added Config that allows device(s) to be published as an external accessory. @@ -70,7 +78,7 @@ All notable changes to this project will be documented in this file. This projec **Full Changelog**: https://github.com/donavanbecker/homebridge-resideo/compare/v1.1.0...v1.2.0 -## [Version 1.1.0](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.1.0) (2022-10-18) +## [1.1.0](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.1.0) (2022-10-18) ## What's Changes - Added Config to allow manually setting firmware version. @@ -78,7 +86,7 @@ All notable changes to this project will be documented in this file. This projec **Full Changelog**: https://github.com/donavanbecker/homebridge-resideo/compare/v1.0.0...v1.1.0 -## [Version 1.0.0](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.0.0) (2022-09-23) +## [1.0.0](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v1.0.0) (2022-09-23) ## What's Changes - Move from `homebridge-honeywell-home` to `homebridge-resideo` diff --git a/LICENSE b/LICENSE index 8318dc07..d483b332 100644 --- a/LICENSE +++ b/LICENSE @@ -1,176 +1,14 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS +ISC License (ISC) +Copyright (c) 2023-2024 donavanbecker + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/config.schema.json b/config.schema.json index 41c80f4f..f196a5c8 100644 --- a/config.schema.json +++ b/config.schema.json @@ -3,6 +3,7 @@ "pluginType": "platform", "singular": true, "customUi": true, + "customUiPath": "./dist/homebridge-ui", "headerDisplay": "

\n\nThe **Homebridge Resideo** plugin allows you to control Resideo thermostats and also view your Leak and Room Sensors from HomeKit. \n\nTo get started link your Resideo account using the button below.", "footerDisplay": "Your Resideo account has been linked. Please raise any issues on our [project page](https://github.com/donavanbecker/homebridge-resideo/issues).\n\nIf you would like to have other Resideo Devices besides Thermostat, Leak Sensors, or Room Sensors added to this plugin fill out [Feature Request Form](https://github.com/donavanbecker/homebridge-resideo/issues/new?assignees=&labels=&template=feature_request.md).", "schema": { @@ -13,6 +14,15 @@ "title": "Name", "default": "Resideo" }, + "port": { + "type": "string", + "title": "Login UI Port", + "default": "8585", + "description": "Port for the Homebridge Resideo UI. Default is 8585.", + "x-schema-form": { + "type": "number" + } + }, "credentials": { "type": "object", "properties": { @@ -85,6 +95,10 @@ { "title": "Leak Sensor", "enum": ["LeakDetector"] + }, + { + "title": "Shut Off Valve", + "enum": ["ShutoffValve"] } ], "condition": { @@ -550,8 +564,9 @@ "key": "options.pushRate", "notitle": true }, - "options.logging" + "options.logging", + "port" ] } ] -} +} \ No newline at end of file diff --git a/homebridge-ui/public/index.html b/homebridge-ui/public/index.html index 1dc19b9c..022cc7de 100644 --- a/homebridge-ui/public/index.html +++ b/homebridge-ui/public/index.html @@ -439,4 +439,4 @@
Disclaimer
homebridge.hideSpinner() } })() - + \ No newline at end of file diff --git a/homebridge-ui/server.js b/homebridge-ui/server.js index e35a5f69..496d9090 100644 --- a/homebridge-ui/server.js +++ b/homebridge-ui/server.js @@ -11,7 +11,6 @@ const exec = util.promisify(require('child_process').exec); const fs = require('fs'); const http = require('http'); const url = require('url'); -const superStringify = require('super-stringify'); class PluginUiServer extends HomebridgePluginUiServer { constructor() { @@ -30,8 +29,8 @@ class PluginUiServer extends HomebridgePluginUiServer { this.secret = query.secret; this.hostname = query.host; const url = 'https://api.honeywell.com/oauth2/authorize?' + - 'response_type=code&redirect_uri=' + encodeURI('http://' + this.hostname + ':8585/auth') + '&' + - 'client_id=' + query.key; + 'response_type=code&redirect_uri=' + encodeURI('http://' + this.hostname + + ':8585/auth') + '&' + 'client_id=' + query.key; res.end(''); break; } @@ -65,7 +64,7 @@ class PluginUiServer extends HomebridgePluginUiServer { res.end('oops.'); } } catch (err) { - res.end('An error occurred:
' + superStringify(err) + '

Close this window and start again'); + res.end('An error occurred:
' + JSON.stringify(err) + '

Close this window and start again'); } } else { res.end('An error occurred:
no code received

Close this window and start again'); @@ -134,4 +133,4 @@ class PluginUiServer extends HomebridgePluginUiServer { } } -(() => new PluginUiServer())(); +(() => new PluginUiServer())(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b638af30..afd35af8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "homebridge-resideo", - "version": "1.4.6", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "homebridge-resideo", - "version": "1.4.6", + "version": "2.0.0", "funding": [ { "type": "Paypal", @@ -17,25 +17,24 @@ "url": "https://github.com/sponsors/donavanbecker" } ], - "license": "Apache-2.0", + "license": "ISC", "dependencies": { "@homebridge/plugin-ui-utils": "^1.0.1", - "axios": "1.6.5", "rxjs": "^7.8.1", - "super-stringify": "^1.0.0" + "undici": "^6.5.0" }, "devDependencies": { - "@types/node": "^20.11.4", - "@typescript-eslint/eslint-plugin": "^6.19.0", - "@typescript-eslint/parser": "^6.19.0", + "@types/node": "^20.11.13", + "@typescript-eslint/eslint-plugin": "^6.20.0", + "@typescript-eslint/parser": "^6.20.0", "eslint": "^8.56.0", "homebridge": "^1.7.0", - "nodemon": "^3.0.2", - "npm-check-updates": "^16.14.12", + "homebridge-config-ui-x": "4.55.1", + "nodemon": "^3.0.3", + "npm-check-updates": "^16.14.14", "rimraf": "^5.0.5", "ts-node": "^10.9.2", - "typescript": "^5.3.3", - "typescript-axios-wb": "^1.0.3" + "typescript": "^5.3.3" }, "engines": { "homebridge": "^1.7.0", @@ -51,6 +50,19 @@ "node": ">=0.10.0" } }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.23.9.tgz", + "integrity": "sha512-oeOFTrYWdWXCvXGB5orvMTJ6gCZ9I6FBjR+M38iKNXCsPxr4xT0RTdg5uz1H7QP8pp74IzPtwritEr+JscqHXQ==", + "dev": true, + "dependencies": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -151,6 +163,198 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fastify/accept-negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz", + "integrity": "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.5.0.tgz", + "integrity": "sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==", + "dev": true, + "dependencies": { + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "fast-uri": "^2.0.0" + } + }, + "node_modules/@fastify/ajv-compiler/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@fastify/ajv-compiler/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/@fastify/busboy": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", + "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==", + "dev": true, + "dependencies": { + "text-decoding": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@fastify/cors": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-8.4.2.tgz", + "integrity": "sha512-IVynbcPG9eWiJ0P/A1B+KynmiU/yTYbu3ooBUSIeHfca/N1XLb9nIJVCws+YTr2q63MA8Y6QLeXQczEv4npM9g==", + "dev": true, + "dependencies": { + "fastify-plugin": "^4.0.0", + "mnemonist": "0.39.5" + } + }, + "node_modules/@fastify/deepmerge": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-1.3.0.tgz", + "integrity": "sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==", + "dev": true + }, + "node_modules/@fastify/error": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", + "integrity": "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==", + "dev": true + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", + "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==", + "dev": true, + "dependencies": { + "fast-json-stringify": "^5.7.0" + } + }, + "node_modules/@fastify/formbody": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@fastify/formbody/-/formbody-7.4.0.tgz", + "integrity": "sha512-H3C6h1GN56/SMrZS8N2vCT2cZr7mIHzBHzOBa5OPpjfB/D6FzP9mMpE02ZzrFX0ANeh0BAJdoXKOF2e7IbV+Og==", + "dev": true, + "dependencies": { + "fast-querystring": "^1.0.0", + "fastify-plugin": "^4.0.0" + } + }, + "node_modules/@fastify/helmet": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@fastify/helmet/-/helmet-11.1.1.tgz", + "integrity": "sha512-pjJxjk6SLEimITWadtYIXt6wBMfFC1I6OQyH/jYVCqSAn36sgAIFjeNiibHtifjCd+e25442pObis3Rjtame6A==", + "dev": true, + "dependencies": { + "fastify-plugin": "^4.2.1", + "helmet": "^7.0.0" + } + }, + "node_modules/@fastify/middie": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@fastify/middie/-/middie-8.3.0.tgz", + "integrity": "sha512-h+zBxCzMlkEkh4fM7pZaSGzqS7P9M0Z6rXnWPdUEPfe7x1BCj++wEk/pQ5jpyYY4pF8AknFqb77n7uwh8HdxEA==", + "dev": true, + "dependencies": { + "@fastify/error": "^3.2.0", + "fastify-plugin": "^4.0.0", + "path-to-regexp": "^6.1.0", + "reusify": "^1.0.4" + } + }, + "node_modules/@fastify/middie/node_modules/path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", + "dev": true + }, + "node_modules/@fastify/multipart": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-8.0.0.tgz", + "integrity": "sha512-xaH1pGIqYnIJjYs5qG6ryhPSFnWuJIfSXYqEUtzmcyREkMk0SwONd2y+SZ9JXfDmETAC/Ogtc/SRbz+AjZhCkw==", + "dev": true, + "dependencies": { + "@fastify/busboy": "^1.0.0", + "@fastify/deepmerge": "^1.0.0", + "@fastify/error": "^3.0.0", + "@fastify/swagger": "^8.3.1", + "@fastify/swagger-ui": "^1.8.0", + "fastify-plugin": "^4.0.0", + "secure-json-parse": "^2.4.0", + "stream-wormhole": "^1.1.0" + } + }, + "node_modules/@fastify/send": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/send/-/send-2.1.0.tgz", + "integrity": "sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==", + "dev": true, + "dependencies": { + "@lukeed/ms": "^2.0.1", + "escape-html": "~1.0.3", + "fast-decode-uri-component": "^1.0.1", + "http-errors": "2.0.0", + "mime": "^3.0.0" + } + }, + "node_modules/@fastify/static": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-6.12.0.tgz", + "integrity": "sha512-KK1B84E6QD/FcQWxDI2aiUCwHxMJBI1KeCUzm1BwYpPY1b742+jeKruGHP2uOluuM6OkBPI8CIANrXcCRtC2oQ==", + "dev": true, + "dependencies": { + "@fastify/accept-negotiator": "^1.0.0", + "@fastify/send": "^2.0.0", + "content-disposition": "^0.5.3", + "fastify-plugin": "^4.0.0", + "glob": "^8.0.1", + "p-limit": "^3.1.0" + } + }, + "node_modules/@fastify/swagger": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-8.14.0.tgz", + "integrity": "sha512-sGiznEb3rl6pKGGUZ+JmfI7ct5cwbTQGo+IjewaTvtzfrshnryu4dZwEsjw0YHABpBA+kCz3kpRaHB7qpa67jg==", + "dev": true, + "dependencies": { + "fastify-plugin": "^4.0.0", + "json-schema-resolver": "^2.0.0", + "openapi-types": "^12.0.0", + "rfdc": "^1.3.0", + "yaml": "^2.2.2" + } + }, + "node_modules/@fastify/swagger-ui": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@fastify/swagger-ui/-/swagger-ui-1.10.2.tgz", + "integrity": "sha512-f2mRqtblm6eRAFQ3e8zSngxVNEtiYY7rISKQVjPA++ZsWc5WYlPVTb6Bx0G/zy0BIoucNqDr/Q2Vb/kTYkOq1A==", + "dev": true, + "dependencies": { + "@fastify/static": "^6.0.0", + "fastify-plugin": "^4.0.0", + "openapi-types": "^12.0.2", + "rfdc": "^1.3.0", + "yaml": "^2.2.2" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -199,6 +403,17 @@ "integrity": "sha512-i5Df8R63XNPCn+Nj1OgAoRdw9e+jHUQb3CNUbvJneI2iu3j4+OtzQj+5PA1Ce+747NR1SPqZSvyvD483dOT3AA==", "dev": true }, + "node_modules/@homebridge/node-pty-prebuilt-multiarch": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/@homebridge/node-pty-prebuilt-multiarch/-/node-pty-prebuilt-multiarch-0.11.12.tgz", + "integrity": "sha512-AHAqVWqWjMxNld+6mpNi/WtwvtskGNJkqxj1EBeMEmxt/0mEY3L109qZE665gc8l+5mo5Whgw64bdl5diQrtag==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "nan": "^2.18.0", + "prebuild-install": "^7.1.1" + } + }, "node_modules/@homebridge/plugin-ui-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@homebridge/plugin-ui-utils/-/plugin-ui-utils-1.0.1.tgz", @@ -366,6 +581,253 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/axios": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.1.tgz", + "integrity": "sha512-VlOZhAGDmOoFdsmewn8AyClAdGpKXQQaY1+3PGB+g6ceurGIdTxZgRX3VXc1T6Zs60PedWjg3A82TDOB05mrzQ==", + "dev": true, + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", + "axios": "^1.3.1", + "reflect-metadata": "^0.1.12", + "rxjs": "^6.0.0 || ^7.0.0" + } + }, + "node_modules/@nestjs/common": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.0.tgz", + "integrity": "sha512-DGv34UHsZBxCM3H5QGE2XE/+oLJzz5+714JQjBhjD9VccFlQs3LRxo/epso4l7nJIiNlZkPyIUC8WzfU/5RTsQ==", + "dev": true, + "dependencies": { + "iterare": "1.2.1", + "tslib": "2.6.2", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/core": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.0.tgz", + "integrity": "sha512-N06P5ncknW/Pm8bj964WvLIZn2gNhHliCBoAO1LeBvNImYkecqKcrmLbY49Fa1rmMfEM3MuBHeDys3edeuYAOA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@nuxtjs/opencollective": "0.3.2", + "fast-safe-stringify": "2.1.1", + "iterare": "1.2.1", + "path-to-regexp": "3.2.0", + "tslib": "2.6.2", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/microservices": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "reflect-metadata": "^0.1.12", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + }, + "@nestjs/websockets": { + "optional": true + } + } + }, + "node_modules/@nestjs/jwt": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-10.2.0.tgz", + "integrity": "sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==", + "dev": true, + "dependencies": { + "@types/jsonwebtoken": "9.0.5", + "jsonwebtoken": "9.0.2" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/@nestjs/mapped-types": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.4.tgz", + "integrity": "sha512-xl+gUSp0B+ln1VSNoUftlglk8dfpUes3DHGxKZ5knuBxS5g2H/8p9/DSBOYWUfO5f4u9s6ffBPZ71WO+tbe5SA==", + "dev": true, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "class-transformer": "^0.4.0 || ^0.5.0", + "class-validator": "^0.13.0 || ^0.14.0", + "reflect-metadata": "^0.1.12" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/passport": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-10.0.3.tgz", + "integrity": "sha512-znJ9Y4S8ZDVY+j4doWAJ8EuuVO7SkQN3yOBmzxbGaXbvcSwFDAdGJ+OMCg52NdzIO4tQoN4pYKx8W6M0ArfFRQ==", + "dev": true, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "passport": "^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0" + } + }, + "node_modules/@nestjs/platform-fastify": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@nestjs/platform-fastify/-/platform-fastify-10.3.0.tgz", + "integrity": "sha512-ka4r/cPWM5y/dXoi9dj6pn1o3WLnfImy2bT3aYVasiDsJff2cd3h/ThugwxjdH0BHUpLSPnawEGzADAcO8Fqug==", + "dev": true, + "dependencies": { + "@fastify/cors": "8.4.2", + "@fastify/formbody": "7.4.0", + "@fastify/middie": "8.3.0", + "fastify": "4.25.1", + "light-my-request": "5.11.0", + "path-to-regexp": "3.2.0", + "tslib": "2.6.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@fastify/static": "^6.0.0", + "@fastify/view": "^7.0.0 || ^8.0.0", + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0" + }, + "peerDependenciesMeta": { + "@fastify/static": { + "optional": true + }, + "@fastify/view": { + "optional": true + } + } + }, + "node_modules/@nestjs/platform-socket.io": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.0.tgz", + "integrity": "sha512-Rdpk9OdsvJfsmVRtg4/0+cUdvOgBEb3F4zo2r4SBgxb0eaR3BHbhbXTJH/U7NvREIvvYbtSNoWI+h2taUEkXwg==", + "dev": true, + "dependencies": { + "socket.io": "4.7.2", + "tslib": "2.6.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/swagger": { + "version": "7.1.17", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.1.17.tgz", + "integrity": "sha512-ASCxBrvMEN2o/8vEEmrIPMNzrr/hVi7QIR4y1oNYvoBNXHuwoF1VSI3+4Rq/3xmwVnVveJxHlBIs2u5xY9VgGQ==", + "dev": true, + "dependencies": { + "@nestjs/mapped-types": "2.0.4", + "js-yaml": "4.1.0", + "lodash": "4.17.21", + "path-to-regexp": "3.2.0", + "swagger-ui-dist": "5.10.3" + }, + "peerDependencies": { + "@fastify/static": "^6.0.0", + "@nestjs/common": "^9.0.0 || ^10.0.0", + "@nestjs/core": "^9.0.0 || ^10.0.0", + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12" + }, + "peerDependenciesMeta": { + "@fastify/static": { + "optional": true + }, + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/websockets": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.0.tgz", + "integrity": "sha512-1cqh46s4iHLytExSWcvp/58pMZFAT7pQ59IAYQCSZz5xFq0lEGxd36C982KyROQIHfno8E+FWm71UhgVTwKsyA==", + "dev": true, + "dependencies": { + "iterare": "1.2.1", + "object-hash": "3.0.0", + "tslib": "2.6.2" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-socket.io": "^10.0.0", + "reflect-metadata": "^0.1.12", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/platform-socket.io": { + "optional": true + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -613,6 +1075,94 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@nuxtjs/opencollective": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz", + "integrity": "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "consola": "^2.15.0", + "node-fetch": "^2.6.1" + }, + "bin": { + "opencollective": "bin/opencollective.js" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/@otplib/core": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", + "integrity": "sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA==", + "dev": true + }, + "node_modules/@otplib/plugin-crypto": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto/-/plugin-crypto-12.0.1.tgz", + "integrity": "sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g==", + "dev": true, + "dependencies": { + "@otplib/core": "^12.0.1" + } + }, + "node_modules/@otplib/plugin-thirty-two": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-thirty-two/-/plugin-thirty-two-12.0.1.tgz", + "integrity": "sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA==", + "dev": true, + "dependencies": { + "@otplib/core": "^12.0.1", + "thirty-two": "^1.0.2" + } + }, + "node_modules/@otplib/preset-default": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/preset-default/-/preset-default-12.0.1.tgz", + "integrity": "sha512-xf1v9oOJRyXfluBhMdpOkr+bsE+Irt+0D5uHtvg6x1eosfmHCsCC6ej/m7FXiWqdo0+ZUI6xSKDhJwc8yfiOPQ==", + "dev": true, + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" + } + }, + "node_modules/@otplib/preset-v11": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/preset-v11/-/preset-v11-12.0.1.tgz", + "integrity": "sha512-9hSetMI7ECqbFiKICrNa4w70deTUfArtwXykPUvSHWOdzOlfa9ajglu7mNCntlvxycTiOAXkQGwjQCzzDEMRMg==", + "dev": true, + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" + } + }, + "node_modules/@oznu/hap-client": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@oznu/hap-client/-/hap-client-1.9.0.tgz", + "integrity": "sha512-dtx8SNLzT62VVwN2YwBVooLyKBrBphddVvYda8CqY5BHLzjK8SMDOF63t2nvp+MLwzWXKfpmcX1w+qS3V8J9Gw==", + "dev": true, + "dependencies": { + "axios": "^0.27.2", + "bonjour-service": "^1.0.12", + "decamelize": "^3.2.0", + "inflection": "^1.13.2", + "source-map-support": "^0.5.19" + } + }, + "node_modules/@oznu/hap-client/node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -724,6 +1274,12 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, "node_modules/@szmarczak/http-timer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", @@ -791,7 +1347,22 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@types/http-cache-semantics": { + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", @@ -803,10 +1374,19 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", + "integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { - "version": "20.11.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.4.tgz", - "integrity": "sha512-6I0fMH8Aoy2lOejL3s4LhyIYX34DPwY8bl5xlNjBvUEk8OHrcuzsFt+Ied4LvJihbtXPM+8zUqdydfIti86v9g==", + "version": "20.11.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.13.tgz", + "integrity": "sha512-5G4zQwdiQBSWYTDAH1ctw2eidqdhMJaNsiIDKHFr55ihz5Trl2qqR8fdrT732yPBho5gkNxXm67OxWFBqX9aPg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -818,17 +1398,23 @@ "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, + "node_modules/@types/validator": { + "version": "13.11.8", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.8.tgz", + "integrity": "sha512-c/hzNDBh7eRF+KbCf+OoZxKbnkpaK/cKp9iLQWqB7muXtM+MtL9SUUH8vCFcLn6dH1Qm05jiexK0ofWY7TfOhQ==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.0.tgz", - "integrity": "sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.20.0.tgz", + "integrity": "sha512-fTwGQUnjhoYHeSF6m5pWNkzmDDdsKELYrOBxhjMrofPqCkoC2k3B2wvGHFxa1CTIqkEn88nlW1HVMztjo2K8Hg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.19.0", - "@typescript-eslint/type-utils": "6.19.0", - "@typescript-eslint/utils": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0", + "@typescript-eslint/scope-manager": "6.20.0", + "@typescript-eslint/type-utils": "6.20.0", + "@typescript-eslint/utils": "6.20.0", + "@typescript-eslint/visitor-keys": "6.20.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -854,15 +1440,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.0.tgz", - "integrity": "sha512-1DyBLG5SH7PYCd00QlroiW60YJ4rWMuUGa/JBV0iZuqi4l4IK3twKPq5ZkEebmGqRjXWVgsUzfd3+nZveewgow==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.20.0.tgz", + "integrity": "sha512-bYerPDF/H5v6V76MdMYhjwmwgMA+jlPVqjSDq2cRqMi8bP5sR3Z+RLOiOMad3nsnmDVmn2gAFCyNgh/dIrfP/w==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.19.0", - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/typescript-estree": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0", + "@typescript-eslint/scope-manager": "6.20.0", + "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/typescript-estree": "6.20.0", + "@typescript-eslint/visitor-keys": "6.20.0", "debug": "^4.3.4" }, "engines": { @@ -882,13 +1468,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.0.tgz", - "integrity": "sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.20.0.tgz", + "integrity": "sha512-p4rvHQRDTI1tGGMDFQm+GtxP1ZHyAh64WANVoyEcNMpaTFn3ox/3CcgtIlELnRfKzSs/DwYlDccJEtr3O6qBvA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0" + "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/visitor-keys": "6.20.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -899,13 +1485,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.0.tgz", - "integrity": "sha512-mcvS6WSWbjiSxKCwBcXtOM5pRkPQ6kcDds/juxcy/727IQr3xMEcwr/YLHW2A2+Fp5ql6khjbKBzOyjuPqGi/w==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.20.0.tgz", + "integrity": "sha512-qnSobiJQb1F5JjN0YDRPHruQTrX7ICsmltXhkV536mp4idGAYrIyr47zF/JmkJtEcAVnIz4gUYJ7gOZa6SmN4g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.19.0", - "@typescript-eslint/utils": "6.19.0", + "@typescript-eslint/typescript-estree": "6.20.0", + "@typescript-eslint/utils": "6.20.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -926,9 +1512,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.0.tgz", - "integrity": "sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.20.0.tgz", + "integrity": "sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -939,13 +1525,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.0.tgz", - "integrity": "sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.20.0.tgz", + "integrity": "sha512-RnRya9q5m6YYSpBN7IzKu9FmLcYtErkDkc8/dKv81I9QiLLtVBHrjz+Ev/crAqgMNW2FCsoZF4g2QUylMnJz+g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0", + "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/visitor-keys": "6.20.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -967,17 +1553,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.0.tgz", - "integrity": "sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.20.0.tgz", + "integrity": "sha512-/EKuw+kRu2vAqCoDwDCBtDRU6CTKbUmwwI7SH7AashZ+W+7o8eiyy6V2cdOqN49KsTcASWsC5QeghYuRDTyOOg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.19.0", - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/typescript-estree": "6.19.0", + "@typescript-eslint/scope-manager": "6.20.0", + "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/typescript-estree": "6.20.0", "semver": "^7.5.4" }, "engines": { @@ -992,12 +1578,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz", - "integrity": "sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.20.0.tgz", + "integrity": "sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/types": "6.20.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1020,6 +1606,37 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "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==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -1103,6 +1720,45 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -1155,6 +1811,12 @@ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "dev": true }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, "node_modules/are-we-there-yet": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", @@ -1211,7 +1873,17 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } }, "node_modules/available-typed-arrays": { "version": "1.0.5", @@ -1225,10 +1897,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/avvio": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.2.1.tgz", + "integrity": "sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "debug": "^4.0.0", + "fastq": "^1.6.1" + } + }, "node_modules/axios": { "version": "1.6.5", "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", + "dev": true, "dependencies": { "follow-redirects": "^1.15.4", "form-data": "^4.0.0", @@ -1241,6 +1925,63 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/bash-color": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/bash-color/-/bash-color-0.0.4.tgz", + "integrity": "sha512-ZNB4525U7BxT6v9C8LEtywyCgB4Pjnm7/bh+ru/Z9Ecxvg3fDjaJ6z305z9a61orQdbB1zqYHh5JbUqx4s4K0g==", + "dev": true + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "dev": true, + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -1250,6 +1991,23 @@ "node": ">=8" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "dev": true + }, "node_modules/bonjour-hap": { "version": "3.6.4", "resolved": "https://registry.npmjs.org/bonjour-hap/-/bonjour-hap-3.6.4.tgz", @@ -1263,6 +2021,18 @@ "multicast-dns-service-types": "^1.1.0" } }, + "node_modules/bonjour-service": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", + "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "dev": true, + "dependencies": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, "node_modules/boxen": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", @@ -1380,12 +2150,66 @@ "node": ">=8" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffer-shims": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha512-Zy8ZXMyxIT6RMTeY7OP/bDndfj6bwCan7SS98CEndS6deHwWPpseeHlwarNcBim+etXnF9HBc1non5JgDaJU1g==", + "dev": true + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "dev": true, + "engines": { + "node": ">=0.2.0" + } + }, "node_modules/builtins": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", @@ -1418,6 +2242,28 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/cacache/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/cacache/node_modules/minipass": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", @@ -1454,6 +2300,18 @@ "node": ">=14.16" } }, + "node_modules/cacheable-request/node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/call-bind": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", @@ -1489,6 +2347,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "dev": true, + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1568,6 +2438,23 @@ "node": ">=8" } }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "dev": true + }, + "node_modules/class-validator": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.0.tgz", + "integrity": "sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==", + "dev": true, + "dependencies": { + "@types/validator": "^13.7.10", + "libphonenumber-js": "^1.10.14", + "validator": "^13.7.0" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -1589,6 +2476,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-table3": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", @@ -1604,6 +2515,15 @@ "@colors/colors": "1.5.0" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1635,6 +2555,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -1692,18 +2613,87 @@ "url": "https://github.com/yeoman/configstore?sponsor=1" } }, + "node_modules/consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "dev": true + }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/core-js-pure": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.35.1.tgz", + "integrity": "sha512-zcIdi/CL3MWbBJYo5YCeVAAx+Sy9yJE9I3/u9LkFABwbeaPhTMRWraM8mYFp9jW5Z50hOy7FVzCc8dCrpZqtIQ==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "dev": true, + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1745,6 +2735,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", + "dev": true + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -1762,6 +2758,18 @@ } } }, + "node_modules/decamelize": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-3.2.0.tgz", + "integrity": "sha512-4TgkVUsmmu7oCSyGBm5FvfMoACuoh9EOidm7V5/J2X2djAwwt57qb3F2KMP2ITqODTCSwb+YRV+0Zqrv18k/hw==", + "dev": true, + "dependencies": { + "xregexp": "^4.2.4" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -1777,18 +2785,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/deep-equal": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", @@ -1836,6 +2832,27 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults/node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", @@ -1880,6 +2897,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "engines": { "node": ">=0.4.0" } @@ -1890,6 +2908,24 @@ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -1911,6 +2947,12 @@ "node": ">=8" } }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -1956,12 +2998,66 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "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/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1978,6 +3074,54 @@ "iconv-lite": "^0.6.2" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -2025,6 +3169,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2216,12 +3366,51 @@ "through": "^2.3.8" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/exponential-backoff": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", "dev": true }, + "node_modules/fast-content-type-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz", + "integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==", + "dev": true + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "dev": true + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2262,6 +3451,43 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "node_modules/fast-json-stringify": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.10.0.tgz", + "integrity": "sha512-fu1BhzPzgOdvK+sVhSPFzm06DQl0Dwbo+NQxWm21k03ili2wsJExXbGZ9qsD4Lsn7zFGltF8h9I1fuhk4JPnrQ==", + "dev": true, + "dependencies": { + "@fastify/deepmerge": "^1.0.0", + "ajv": "^8.10.0", + "ajv-formats": "^2.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.1.0", + "json-schema-ref-resolver": "^1.0.1", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-json-stringify/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/fast-json-stringify/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", @@ -2274,6 +3500,30 @@ "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==", "dev": true }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "dev": true, + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-redact": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.3.0.tgz", + "integrity": "sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, "node_modules/fast-srp-hap": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/fast-srp-hap/-/fast-srp-hap-2.0.4.tgz", @@ -2283,10 +3533,46 @@ "node": ">=10.17.0" } }, + "node_modules/fast-uri": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.3.0.tgz", + "integrity": "sha512-eel5UKGn369gGEWOqBShmFJWfq/xSJvsgDzgLYC845GneayWvXBf0lJCBn5qTABfewy1ZDPoaR5OZCP+kssfuw==", + "dev": true + }, + "node_modules/fastify": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.25.1.tgz", + "integrity": "sha512-D8d0rv61TwqoAS7lom2tvIlgVMlx88lLsiwXyWNjA7CU/LC/mx/Gp2WAlC0S/ABq19U+y/aRvYFG5xLUu2aMrg==", + "dev": true, + "dependencies": { + "@fastify/ajv-compiler": "^3.5.0", + "@fastify/error": "^3.4.0", + "@fastify/fast-json-stringify-compiler": "^4.3.0", + "abstract-logging": "^2.0.1", + "avvio": "^8.2.1", + "fast-content-type-parse": "^1.1.0", + "fast-json-stringify": "^5.8.0", + "find-my-way": "^7.7.0", + "light-my-request": "^5.11.0", + "pino": "^8.17.0", + "process-warning": "^3.0.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.3.0", + "secure-json-parse": "^2.7.0", + "semver": "^7.5.4", + "toad-cache": "^3.3.0" + } + }, + "node_modules/fastify-plugin": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-4.5.1.tgz", + "integrity": "sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==", + "dev": true + }, "node_modules/fastq": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", - "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz", + "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -2316,6 +3602,20 @@ "node": ">=8" } }, + "node_modules/find-my-way": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-7.7.0.tgz", + "integrity": "sha512-+SrHpvQ52Q6W9f3wJoJBbAQULJuNEEQwBvlvYwACDhBTLOTMiQ0HYWh4+vC3OivGP2ENcTI1oKlFA2OepJNjhQ==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^2.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2413,6 +3713,7 @@ "version": "1.15.5", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "dev": true, "funding": [ { "type": "individual", @@ -2453,10 +3754,23 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -2475,6 +3789,15 @@ "node": ">= 14.17" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fp-and-or": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/fp-and-or/-/fp-and-or-0.1.4.tgz", @@ -2490,6 +3813,12 @@ "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", "dev": true }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -2545,6 +3874,75 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/fstream/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/fstream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fstream/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/fstream/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -2591,12 +3989,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/gauge/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, "node_modules/get-intrinsic": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", @@ -2636,23 +4028,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true + }, "node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2670,6 +4065,18 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/global-dirs": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", @@ -2898,6 +4305,15 @@ "node": ">= 0.4" } }, + "node_modules/helmet": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz", + "integrity": "sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/hexy": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/hexy/-/hexy-0.2.11.tgz", @@ -2913,19 +4329,109 @@ "integrity": "sha512-2QikXnmpnFe2s33Q8TeYE5+sXyKHUZ+9l5WfDmpuupHdct6H/G6b6z3HCj+2rlMRKKY5ElLv5XtLoxOcafnL0g==", "dev": true, "dependencies": { - "chalk": "^4.1.2", - "commander": "^7.2.0", - "fs-extra": "^10.1.0", - "hap-nodejs": "~0.11.1", - "qrcode-terminal": "^0.12.0", - "semver": "^7.5.4", - "source-map-support": "^0.5.21" - }, - "bin": { - "homebridge": "bin/homebridge" + "chalk": "^4.1.2", + "commander": "^7.2.0", + "fs-extra": "^10.1.0", + "hap-nodejs": "~0.11.1", + "qrcode-terminal": "^0.12.0", + "semver": "^7.5.4", + "source-map-support": "^0.5.21" + }, + "bin": { + "homebridge": "bin/homebridge" + }, + "engines": { + "node": "^18.15.0 || ^20.7.0" + } + }, + "node_modules/homebridge-config-ui-x": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/homebridge-config-ui-x/-/homebridge-config-ui-x-4.55.1.tgz", + "integrity": "sha512-XojwCOEB6LsJeYt6NFakXGCIs9WFA/xiraVhC1R1+//bQPmv1T1F3sZvrPxp1UTBLKxoxT37SMdtq0us8pRyaw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/oznu" + }, + { + "type": "paypal", + "url": "https://paypal.me/oznu" + } + ], + "dependencies": { + "@fastify/helmet": "11.1.1", + "@fastify/multipart": "8.0.0", + "@fastify/static": "6.12.0", + "@homebridge/node-pty-prebuilt-multiarch": "0.11.12", + "@nestjs/axios": "3.0.1", + "@nestjs/common": "10.3.0", + "@nestjs/core": "10.3.0", + "@nestjs/jwt": "10.2.0", + "@nestjs/passport": "10.0.3", + "@nestjs/platform-fastify": "10.3.0", + "@nestjs/platform-socket.io": "10.3.0", + "@nestjs/swagger": "7.1.17", + "@nestjs/websockets": "10.3.0", + "@oznu/hap-client": "1.9.0", + "axios": "1.6.5", + "bash-color": "0.0.4", + "bonjour-service": "=1.1.1", + "buffer-shims": "1.0.0", + "class-transformer": "0.5.1", + "class-validator": "0.14.0", + "commander": "11.1.0", + "dayjs": "1.11.10", + "fastify": "4.25.1", + "fs-extra": "11.2.0", + "jsonwebtoken": "9.0.2", + "lodash": "4.17.21", + "node-cache": "5.1.2", + "node-schedule": "2.1.1", + "ora": "5.4.1", + "otplib": "12.0.1", + "p-limit": "3.1.0", + "passport": "0.7.0", + "passport-jwt": "4.0.1", + "reflect-metadata": "0.1.14", + "rxjs": "7.8.1", + "semver": "7.5.4", + "systeminformation": "5.21.22", + "tail": "2.2.6", + "tar": "6.2.0", + "tcp-port-used": "1.0.2", + "unzipper": "0.10.14" + }, + "bin": { + "hb-service": "dist/bin/hb-service.js", + "homebridge-config-ui-x": "dist/bin/standalone.js" + }, + "engines": { + "homebridge": "^1.6.0", + "node": "^18 || ^20" + } + }, + "node_modules/homebridge-config-ui-x/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/homebridge-config-ui-x/node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": "^18.15.0 || ^20.7.0" + "node": ">=14.14" } }, "node_modules/hosted-git-info": { @@ -2946,6 +4452,22 @@ "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "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-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", @@ -3008,6 +4530,26 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", @@ -3084,6 +4626,15 @@ "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", "dev": true }, + "node_modules/inflection": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz", + "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==", + "dev": true, + "engines": [ + "node >= 0.4.0" + ] + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -3129,6 +4680,24 @@ "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", "dev": true }, + "node_modules/ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -3296,6 +4865,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", @@ -3453,6 +5031,24 @@ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true + }, "node_modules/is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", @@ -3484,6 +5080,20 @@ "node": ">=12" } }, + "node_modules/is2": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", + "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "ip-regex": "^4.1.0", + "is-url": "^1.2.4" + }, + "engines": { + "node": ">=v0.10.0" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -3496,6 +5106,15 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/iterare": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", + "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/jackspeak": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", @@ -3556,6 +5175,32 @@ "jju": "^1.1.0" } }, + "node_modules/json-schema-ref-resolver": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz", + "integrity": "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/json-schema-resolver": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-schema-resolver/-/json-schema-resolver-2.0.0.tgz", + "integrity": "sha512-pJ4XLQP4Q9HTxl6RVDLJ8Cyh1uitSs0CzDBAz1uoJ4sRD/Bk7cFSXL1FUXDW3zJ7YnfliJx6eu8Jn283bpZ4Yg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "rfdc": "^1.1.4", + "uri-js": "^4.2.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/Eomm/json-schema-resolver?sponsor=1" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3607,6 +5252,49 @@ "node >= 0.2.0" ] }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dev": true, + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3653,6 +5341,35 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.10.54", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.54.tgz", + "integrity": "sha512-P+38dUgJsmh0gzoRDoM4F5jLbyfztkU6PY6eSK6S5HwTi/LPvnwXqVCQZlAy1FxZ5c48q25QhxGQ0pq+WQcSlQ==", + "dev": true + }, + "node_modules/light-my-request": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.11.0.tgz", + "integrity": "sha512-qkFCeloXCOMpmEdZ/MV91P8AT4fjwFXWaAFz3lUeStM8RcoM1ks4J/F8r1b3r6y/H4u3ACEJ1T+Gv5bopj7oDA==", + "dev": true, + "dependencies": { + "cookie": "^0.5.0", + "process-warning": "^2.0.0", + "set-cookie-parser": "^2.4.1" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.2.tgz", + "integrity": "sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA==", + "dev": true + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "dev": true + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3674,12 +5391,76 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==", + "dev": true + }, "node_modules/lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -3701,6 +5482,15 @@ "node": ">=12" } }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -3761,10 +5551,23 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -3773,6 +5576,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -3780,13 +5584,22 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4006,6 +5819,21 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/mnemonist": { + "version": "0.39.5", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.5.tgz", + "integrity": "sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==", + "dev": true, + "dependencies": { + "obliterator": "^2.0.1" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4031,6 +5859,18 @@ "integrity": "sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ==", "dev": true }, + "node_modules/nan": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", + "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", + "dev": true + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4046,6 +5886,50 @@ "node": ">= 0.6" } }, + "node_modules/node-abi": { + "version": "3.54.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.54.0.tgz", + "integrity": "sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "dev": true, + "dependencies": { + "clone": "2.x" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-gyp": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", @@ -4336,10 +6220,24 @@ "q": "~1.1.1" } }, + "node_modules/node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "dev": true, + "dependencies": { + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/nodemon": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.2.tgz", - "integrity": "sha512-9qIN2LNTrEzpOPBaWHTm4Asy1LxXLSickZStAQ4IZe7zsoIpD/A7LWxhZV3t4Zu352uBcqVnRsDXSMR2Sc3lTA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.3.tgz", + "integrity": "sha512-7jH/NXbFPxVaMwmBCC2B9F/V6X1VkEdNgx3iu9jji8WxWcvhMWkmhNWhI5077zknOnZnBzba9hZP6bCPJLSReQ==", "dev": true, "dependencies": { "chokidar": "^3.5.2", @@ -4483,9 +6381,9 @@ } }, "node_modules/npm-check-updates": { - "version": "16.14.12", - "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.12.tgz", - "integrity": "sha512-5FvqaDX8AqWWTDQFbBllgLwoRXTvzlqVIRSKl9Kg8bYZTfNwMnrp1Zlmb5e/ocf11UjPTc+ShBFjYQ7kg6FL0w==", + "version": "16.14.14", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.14.tgz", + "integrity": "sha512-Y3ajS/Ep40jM489rLBdz9jehn/BMil5s9fA4PSr2ZJxxSmtLWCSmRqsI2IEZ9Nb3MTMu8a3s7kBs0l+JbjdkTA==", "dev": true, "dependencies": { "chalk": "^5.3.0", @@ -4697,6 +6595,24 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -4749,6 +6665,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obliterator": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==", + "dev": true + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4758,6 +6689,27 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "dev": true + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -4775,6 +6727,40 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/otplib": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/otplib/-/otplib-12.0.1.tgz", + "integrity": "sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg==", + "dev": true, + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/preset-default": "^12.0.1", + "@otplib/preset-v11": "^12.0.1" + } + }, "node_modules/p-cancelable": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", @@ -4903,6 +6889,43 @@ "node": ">=0.10.0" } }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "dev": true, + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "dev": true, + "dependencies": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4947,14 +6970,20 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", - "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "dev": true, "engines": { "node": "14 || >=16.14" } }, + "node_modules/path-to-regexp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", + "integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==", + "dev": true + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -4964,6 +6993,12 @@ "node": ">=8" } }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==", + "dev": true + }, "node_modules/pause-stream": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", @@ -4985,6 +7020,110 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pino": { + "version": "8.17.2", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.17.2.tgz", + "integrity": "sha512-LA6qKgeDMLr2ux2y/YiUt47EfgQ+S9LznBWOJdN3q1dx2sv0ziDLUBeVpyVv17TEcGCBuWf0zNtg3M5m1NhhWQ==", + "dev": true, + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "v1.1.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^3.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.7.0", + "thread-stream": "^2.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz", + "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==", + "dev": true, + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-abstract-transport/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/pino-abstract-transport/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dev": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==", + "dev": true + }, + "node_modules/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5003,6 +7142,27 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", + "dev": true + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -5050,10 +7210,24 @@ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "dev": true }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "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==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true }, "node_modules/pstree.remy": { "version": "1.1.8", @@ -5061,6 +7235,16 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5124,6 +7308,12 @@ } ] }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "dev": true + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -5206,6 +7396,28 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/read-package-json/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -5232,6 +7444,27 @@ "node": ">=8.10.0" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", + "dev": true + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, "node_modules/regexp.prototype.flags": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", @@ -5324,6 +7557,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -5343,6 +7598,12 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "dev": true + }, "node_modules/rimraf": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", @@ -5361,6 +7622,28 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "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", @@ -5412,6 +7695,24 @@ } ] }, + "node_modules/safe-regex2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", + "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", + "dev": true, + "dependencies": { + "ret": "~0.2.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -5425,6 +7726,12 @@ "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", "dev": true }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "dev": true + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -5479,6 +7786,12 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, + "node_modules/set-cookie-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", + "dev": true + }, "node_modules/set-function-length": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", @@ -5509,6 +7822,18 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5545,34 +7870,73 @@ } }, "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sigstore": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.9.0.tgz", + "integrity": "sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A==", "dev": true, - "engines": { - "node": ">=14" + "dependencies": { + "@sigstore/bundle": "^1.1.0", + "@sigstore/protobuf-specs": "^0.2.0", + "@sigstore/sign": "^1.0.0", + "@sigstore/tuf": "^1.0.3", + "make-fetch-happen": "^11.0.1" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "bin": { + "sigstore": "bin/sigstore.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/sigstore": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.9.0.tgz", - "integrity": "sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A==", + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", "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": { - "@sigstore/bundle": "^1.1.0", - "@sigstore/protobuf-specs": "^0.2.0", - "@sigstore/sign": "^1.0.0", - "@sigstore/tuf": "^1.0.3", - "make-fetch-happen": "^11.0.1" - }, - "bin": { - "sigstore": "bin/sigstore.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" } }, "node_modules/simple-update-notifier": { @@ -5612,6 +7976,46 @@ "npm": ">= 3.0.0" } }, + "node_modules/socket.io": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", + "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dev": true, + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/socks": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", @@ -5646,6 +8050,21 @@ "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "dev": true }, + "node_modules/sonic-boom": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.0.tgz", + "integrity": "sha512-ybz6OYOUjoQQCQ/i4LU8kaToD8ACtYP+Cj5qd2AO36bwbdewxWJ3ArmJ2cr6AvxlL2o0PqnCcPGUgkILbfkaCA==", + "dev": true, + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==", + "dev": true + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5688,9 +8107,9 @@ } }, "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.4.0.tgz", + "integrity": "sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==", "dev": true }, "node_modules/spdx-expression-parse": { @@ -5721,6 +8140,15 @@ "node": "*" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "engines": { + "node": ">= 10.x" + } + }, "node_modules/ssri": { "version": "10.0.5", "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.5.tgz", @@ -5742,6 +8170,15 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -5764,6 +8201,15 @@ "through": "~2.3.4" } }, + "node_modules/stream-wormhole": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stream-wormhole/-/stream-wormhole-1.1.0.tgz", + "integrity": "sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -5839,11 +8285,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/super-stringify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/super-stringify/-/super-stringify-1.0.0.tgz", - "integrity": "sha512-rWfRZG1oe8T9Ai9HiRpkfPynTj0jfpxKusXDeYyCrCEqUhpZgOYqF0HhP/oNAjWnkYZb1Lp501fN3XkjC3ejMQ==" - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5856,6 +8297,47 @@ "node": ">=8" } }, + "node_modules/swagger-ui-dist": { + "version": "5.10.3", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.10.3.tgz", + "integrity": "sha512-fu3aozjxFWsmcO1vyt1q1Ji2kN7KlTd1vHy27E9WgPyXo9nrEzhQPqgxaAjbMsOmb8XFKNGo4Sa3Q+84Fh+pFw==", + "dev": true + }, + "node_modules/systeminformation": { + "version": "5.21.22", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.21.22.tgz", + "integrity": "sha512-gNHloAJSyS+sKWkwvmvozZ1eHrdVTEsynWMTY6lvLGBB70gflkBQFw8drXXr1oEXY84+Vr9tOOrN8xHZLJSycA==", + "dev": true, + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" + } + }, + "node_modules/tail": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/tail/-/tail-2.2.6.tgz", + "integrity": "sha512-IQ6G4wK/t8VBauYiGPLx+d3fA5XjSVagjWV5SIYzvEvglbQjwEcukeYI68JOPpdydjxhZ9sIgzRlSmwSpphHyw==", + "dev": true, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/tar": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", @@ -5873,6 +8355,40 @@ "node": ">=10" } }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tar/node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -5909,12 +8425,63 @@ "node": ">=10" } }, + "node_modules/tcp-port-used": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", + "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", + "dev": true, + "dependencies": { + "debug": "4.3.1", + "is2": "^2.0.6" + } + }, + "node_modules/tcp-port-used/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/text-decoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", + "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==", + "dev": true + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/thirty-two": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", + "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==", + "dev": true, + "engines": { + "node": ">=0.2.6" + } + }, + "node_modules/thread-stream": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz", + "integrity": "sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==", + "dev": true, + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -5939,6 +8506,24 @@ "node": ">=8.0" } }, + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -5966,6 +8551,21 @@ "node": "*" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -6040,6 +8640,18 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", @@ -6092,13 +8704,16 @@ "node": ">=14.17" } }, - "node_modules/typescript-axios-wb": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typescript-axios-wb/-/typescript-axios-wb-1.0.3.tgz", - "integrity": "sha512-lS0aojPRHE5nx1gglcqYXfzoEQWgxmMfXbTbozivTNJM6PfCS3a5sHfQPmxy7WQS1piQdGExB3/3GdHvtMDiqw==", + "node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", "dev": true, + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, "engines": { - "node": ">=6.0.0" + "node": ">=8" } }, "node_modules/undefsafe": { @@ -6107,12 +8722,31 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, + "node_modules/undici": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.5.0.tgz", + "integrity": "sha512-/MUmPb2ptTvp1j7lPvdMSofMdqPxcOhAaKZi4k55sqm6XMeKI3n1dZJ5cnD4gLjpt2l7CIlthR1IXM59xKhpxw==", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=18.0" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true }, + "node_modules/undici/node_modules/@fastify/busboy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", + "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==", + "engines": { + "node": ">=14" + } + }, "node_modules/unique-filename": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", @@ -6170,6 +8804,60 @@ "node": ">=8" } }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/unzipper/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/unzipper/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "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/unzipper/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/unzipper/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/update-notifier": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", @@ -6225,6 +8913,15 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, + "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==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -6253,6 +8950,49 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6507,11 +9247,26 @@ "typedarray-to-buffer": "^3.1.5" } }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } }, "node_modules/xdg-basedir": { "version": "5.1.0", @@ -6547,12 +9302,30 @@ "node": ">=4.0" } }, + "node_modules/xregexp": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.4.1.tgz", + "integrity": "sha512-2u9HwfadaJaY9zHtRRnH6BY6CQVNQKkYm3oLtC9gJXXzfsbACg5X5e4EZZGVAH+YIfa+QA9lsFQTTe3HURF3ag==", + "dev": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.12.1" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 97e30420..36e0b832 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,14 @@ { "displayName": "Resideo", "name": "homebridge-resideo", - "version": "1.4.6", + "version": "2.0.0", "description": "The Resideo plugin allows you to access your Resideo device(s) from HomeKit.", - "author": "donavanbecker", - "license": "Apache-2.0", + "author": { + "name": "donavanbecker", + "url": "https://github.com/donavanbecker" + }, + "type": "module", + "license": "ISC", "icon": "https://raw.githubusercontent.com/donavanbecker/homebridge-resideo/beta-1.4.4/branding/icon.png", "repository": { "type": "git", @@ -21,11 +25,14 @@ "scripts": { "check": "npm install && npm outdated", "update": "ncu -u && npm update && npm install", + "update dependencies": "npm run check && npm run update", "lint": "eslint src/**.ts", - "watch": "npm run build && npm link && nodemon", + "jlint": "eslint homebridge-ui/public/**.mjs", + "watch": "npm run build && npm run plugin-ui && npm link && nodemon", + "plugin-ui": "rsync ./src/homebridge-ui/public/index.html ./dist/homebridge-ui/public/", "build": "rimraf ./dist && tsc", - "prepublishOnly": "npm run update && npm run lint && npm run build", "postpublish": "npm run clean", + "prepublishOnly": "npm run lint && npm run build && npm run plugin-ui", "clean": "rimraf ./dist", "test": "eslint src/**.ts" }, @@ -58,22 +65,21 @@ "leak" ], "dependencies": { - "axios": "1.6.5", - "rxjs": "^7.8.1", "@homebridge/plugin-ui-utils": "^1.0.1", - "super-stringify": "^1.0.0" + "rxjs": "^7.8.1", + "undici": "^6.5.0" }, "devDependencies": { - "@types/node": "^20.11.4", - "@typescript-eslint/eslint-plugin": "^6.19.0", - "@typescript-eslint/parser": "^6.19.0", + "@types/node": "^20.11.13", + "@typescript-eslint/eslint-plugin": "^6.20.0", + "@typescript-eslint/parser": "^6.20.0", "eslint": "^8.56.0", "homebridge": "^1.7.0", - "nodemon": "^3.0.2", + "homebridge-config-ui-x": "4.55.1", + "nodemon": "^3.0.3", + "npm-check-updates": "^16.14.14", "rimraf": "^5.0.5", "ts-node": "^10.9.2", - "typescript": "^5.3.3", - "typescript-axios-wb": "^1.0.3", - "npm-check-updates": "^16.14.12" + "typescript": "^5.3.3" } } diff --git a/src/devices/leaksensors.ts b/src/devices/leaksensors.ts index a4d076fb..40b06634 100644 --- a/src/devices/leaksensors.ts +++ b/src/devices/leaksensors.ts @@ -1,9 +1,9 @@ -import { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import { request } from 'undici'; import { interval, Subject } from 'rxjs'; -import superStringify from 'super-stringify'; import { skipWhile, take } from 'rxjs/operators'; -import { ResideoPlatform } from '../platform'; -import * as settings from '../settings'; +import { ResideoPlatform } from '../platform.js'; +import { Service, PlatformAccessory, CharacteristicValue, HAP, API, Logging } from 'homebridge'; +import { DeviceURL, ResideoPlatformConfig, devicesConfig, location, resideoDevice } from '../settings.js'; /** * Platform Accessory @@ -11,6 +11,10 @@ import * as settings from '../settings'; * Each accessory may expose multiple services of different service types. */ export class LeakSensor { + public readonly api: API; + public readonly log: Logging; + public readonly config!: ResideoPlatformConfig; + protected readonly hap: HAP; // Services service: Service; temperatureService?: Service; @@ -33,6 +37,7 @@ export class LeakSensor { humidity!: number; batteryRemaining!: number; waterPresent!: boolean; + debugMode!: boolean; // Config deviceLogging!: string; @@ -44,39 +49,50 @@ export class LeakSensor { constructor( private readonly platform: ResideoPlatform, - private accessory: PlatformAccessory, - public readonly locationId: settings.location['locationID'], - public device: settings.device & settings.devicesConfig, + private readonly accessory: PlatformAccessory, + public readonly locationId: location['locationID'], + public device: resideoDevice & devicesConfig, ) { - this.logs(device); - this.refreshRate(device); - this.config(device); + this.api = this.platform.api; + this.log = this.platform.log; + this.config = this.platform.config; + this.hap = this.api.hap; + + this.StatusActive = accessory.context.StatusActive || false; + this.LeakDetected = accessory.context.LeakDetected || this.hap.Characteristic.LeakDetected.LEAK_NOT_DETECTED; + this.BatteryLevel = accessory.context.BatteryLevel || 100; + this.ChargingState = accessory.context.ChargingState || this.hap.Characteristic.ChargingState.NOT_CHARGING; + this.StatusLowBattery = accessory.context.StatusLowBattery || this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; + this.CurrentTemperature = accessory.context.CurrentTemperature || 20; + this.CurrentRelativeHumidity = accessory.context.CurrentRelativeHumidity || 50; + accessory.context.FirmwareRevision = 'v2.0.0'; + + this.deviceLogs(); + // this is subject we use to track when we need to POST changes to the Resideo API this.doSensorUpdate = new Subject(); this.SensorUpdateInProgress = false; // set accessory information accessory - .getService(this.platform.Service.AccessoryInformation)! - .setCharacteristic(this.platform.Characteristic.Manufacturer, 'Resideo') - .setCharacteristic(this.platform.Characteristic.Model, device.deviceType) - .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceID) - .setCharacteristic(this.platform.Characteristic.FirmwareRevision, accessory.context.firmwareRevision) - .getCharacteristic(this.platform.Characteristic.FirmwareRevision) - .updateValue(accessory.context.firmwareRevision); + .getService(this.hap.Service.AccessoryInformation)! + .setCharacteristic(this.hap.Characteristic.Manufacturer, 'Resideo') + .setCharacteristic(this.hap.Characteristic.Model, device.deviceType) + .setCharacteristic(this.hap.Characteristic.SerialNumber, device.deviceID) + .setCharacteristic(this.hap.Characteristic.FirmwareRevision, accessory.context.firmwareRevision || 'v2.0.0'); // get the LightBulb service if it exists, otherwise create a new LightBulb service // you can create multiple services for each accessory - (this.service = this.accessory.getService(this.platform.Service.Battery) || this.accessory.addService(this.platform.Service.Battery)), + (this.service = this.accessory.getService(this.hap.Service.Battery) || this.accessory.addService(this.hap.Service.Battery)), `${accessory.displayName} Battery`; // To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error, // when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id: - // this.accessory.getService('NAME') ?? this.accessory.addService(this.platform.Service.Lightbulb, 'NAME', 'USER_DEFINED_SUBTYPE'); + // this.accessory.getService('NAME') ?? this.accessory.addService(this.hap.Service.Lightbulb, 'NAME', 'USER_DEFINED_SUBTYPE'); // set the service name, this is what is displayed as the default name on the Home app // in this example we are using the name we stored in the `accessory.context` in the `discoverDevices` method. - this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.displayName); + this.service.setCharacteristic(this.hap.Characteristic.Name, accessory.displayName); // each service must implement at-minimum the "required characteristics" for the given service type // see https://developers.homebridge.io/#/service/ @@ -85,38 +101,38 @@ export class LeakSensor { this.parseStatus(); // Set Charging State - this.service.setCharacteristic(this.platform.Characteristic.ChargingState, 2); + this.service.setCharacteristic(this.hap.Characteristic.ChargingState, 2); // Leak Sensor Service if (this.device.leaksensor?.hide_leak) { - this.debugLog(`Leak Sensor: ${accessory.displayName} Removing Leak Sensor Service`); - this.leakService = this.accessory.getService(this.platform.Service.LeakSensor); + this.log.debug(`Leak Sensor: ${accessory.displayName} Removing Leak Sensor Service`); + this.leakService = this.accessory.getService(this.hap.Service.LeakSensor); accessory.removeService(this.leakService!); } else if (!this.leakService) { - this.debugLog(`Leak Sensor: ${accessory.displayName} Add Leak Sensor Service`); - (this.leakService = this.accessory.getService(this.platform.Service.LeakSensor) || this.accessory.addService(this.platform.Service.LeakSensor)), - `${accessory.displayName} Leak Sensor`; + this.log.debug(`Leak Sensor: ${accessory.displayName} Add Leak Sensor Service`); + (this.leakService = this.accessory.getService(this.hap.Service.LeakSensor) + || this.accessory.addService(this.hap.Service.LeakSensor)), `${accessory.displayName} Leak Sensor`; - this.leakService.setCharacteristic(this.platform.Characteristic.Name, `${accessory.displayName} Leak Sensor`); + this.leakService.setCharacteristic(this.hap.Characteristic.Name, `${accessory.displayName} Leak Sensor`); } else { - this.debugLog(`Leak Sensor: ${accessory.displayName} Leak Sensor Service Not Added`); + this.log.debug(`Leak Sensor: ${accessory.displayName} Leak Sensor Service Not Added`); } // Temperature Sensor Service if (this.device.leaksensor?.hide_temperature) { - this.debugLog(`Leak Sensor: ${accessory.displayName} Removing Temperature Sensor Service`); - this.temperatureService = this.accessory.getService(this.platform.Service.TemperatureSensor); + this.log.debug(`Leak Sensor: ${accessory.displayName} Removing Temperature Sensor Service`); + this.temperatureService = this.accessory.getService(this.hap.Service.TemperatureSensor); accessory.removeService(this.temperatureService!); } else if (!this.temperatureService) { - this.debugLog(`Leak Sensor: ${accessory.displayName} Add Temperature Sensor Service`); + this.log.debug(`Leak Sensor: ${accessory.displayName} Add Temperature Sensor Service`); (this.temperatureService = - this.accessory.getService(this.platform.Service.TemperatureSensor) || this.accessory.addService(this.platform.Service.TemperatureSensor)), + this.accessory.getService(this.hap.Service.TemperatureSensor) || this.accessory.addService(this.hap.Service.TemperatureSensor)), `${accessory.displayName} Temperature Sensor`; - this.temperatureService.setCharacteristic(this.platform.Characteristic.Name, `${accessory.displayName} Temperature Sensor`); + this.temperatureService.setCharacteristic(this.hap.Characteristic.Name, `${accessory.displayName} Temperature Sensor`); this.temperatureService - .getCharacteristic(this.platform.Characteristic.CurrentTemperature) + .getCharacteristic(this.hap.Characteristic.CurrentTemperature) .setProps({ minValue: -273.15, maxValue: 100, @@ -126,24 +142,24 @@ export class LeakSensor { return this.CurrentTemperature; }); } else { - this.debugLog(`Leak Sensor: ${accessory.displayName} Temperature Sensor Service Not Added`); + this.log.debug(`Leak Sensor: ${accessory.displayName} Temperature Sensor Service Not Added`); } // Humidity Sensor Service if (this.device.leaksensor?.hide_humidity) { - this.debugLog(`Leak Sensor: ${accessory.displayName} Removing Humidity Sensor Service`); - this.humidityService = this.accessory.getService(this.platform.Service.HumiditySensor); + this.log.debug(`Leak Sensor: ${accessory.displayName} Removing Humidity Sensor Service`); + this.humidityService = this.accessory.getService(this.hap.Service.HumiditySensor); accessory.removeService(this.humidityService!); } else if (!this.humidityService) { - this.debugLog(`Leak Sensor: ${accessory.displayName} Add Humidity Sensor Service`); + this.log.debug(`Leak Sensor: ${accessory.displayName} Add Humidity Sensor Service`); (this.humidityService = - this.accessory.getService(this.platform.Service.HumiditySensor) || this.accessory.addService(this.platform.Service.HumiditySensor)), + this.accessory.getService(this.hap.Service.HumiditySensor) || this.accessory.addService(this.hap.Service.HumiditySensor)), `${accessory.displayName} Humidity Sensor`; - this.humidityService.setCharacteristic(this.platform.Characteristic.Name, `${accessory.displayName} Humidity Sensor`); + this.humidityService.setCharacteristic(this.hap.Characteristic.Name, `${accessory.displayName} Humidity Sensor`); this.humidityService - .getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity) + .getCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity) .setProps({ minStep: 0.1, }) @@ -151,7 +167,7 @@ export class LeakSensor { return this.CurrentRelativeHumidity; }); } else { - this.debugLog(`Leak Sensor: ${accessory.displayName} Humidity Sensor Service Not Added`); + this.log.debug(`Leak Sensor: ${accessory.displayName} Humidity Sensor Service Not Added`); } // Retrieve initial values and updateHomekit @@ -159,7 +175,7 @@ export class LeakSensor { this.updateHomeKitCharacteristics(); // Start an update interval - interval(this.platform.config.options!.refreshRate! * 1000) + interval(this.config.options!.refreshRate! * 1000) .pipe(skipWhile(() => this.SensorUpdateInProgress)) .subscribe(async () => { await this.refreshStatus(); @@ -178,29 +194,29 @@ export class LeakSensor { } else { this.LeakDetected = 0; } - this.debugLog(`Leak Sensor: ${this.accessory.displayName} LeakDetected: ${this.LeakDetected}`); + this.log.debug(`Leak Sensor: ${this.accessory.displayName} LeakDetected: ${this.LeakDetected}`); // Temperature Service if (!this.device.leaksensor?.hide_temperature) { this.CurrentTemperature = this.temperature; - this.debugLog(`Leak Sensor: ${this.accessory.displayName} CurrentTemperature: ${this.CurrentTemperature}°`); + this.log.debug(`Leak Sensor: ${this.accessory.displayName} CurrentTemperature: ${this.CurrentTemperature}°`); } // Humidity Service if (!this.device.leaksensor?.hide_humidity) { this.CurrentRelativeHumidity = this.humidity; - this.debugLog(`Leak Sensor: ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}%`); + this.log.debug(`Leak Sensor: ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}%`); } // Battery Service this.BatteryLevel = Number(this.device.batteryRemaining); - this.service.getCharacteristic(this.platform.Characteristic.BatteryLevel).updateValue(this.BatteryLevel); + this.service.getCharacteristic(this.hap.Characteristic.BatteryLevel).updateValue(this.BatteryLevel); if (this.device.batteryRemaining < 15) { - this.StatusLowBattery = this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW; + this.StatusLowBattery = this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW; } else { - this.StatusLowBattery = this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; + this.StatusLowBattery = this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; } - this.debugLog(`Leak Sensor: ${this.accessory.displayName} BatteryLevel: ${this.BatteryLevel},` + ` StatusLowBattery: ${this.StatusLowBattery}`); + this.log.debug(`Leak Sensor: ${this.accessory.displayName} BatteryLevel: ${this.BatteryLevel},` + ` StatusLowBattery: ${this.StatusLowBattery}`); } /** @@ -208,20 +224,31 @@ export class LeakSensor { */ async refreshStatus(): Promise { try { - const device: any = ( - await this.platform.axios.get(`${settings.DeviceURL}/waterLeakDetectors/${this.device.deviceID}`, { - params: { - locationId: this.locationId, - }, - }) - ).data; + const { body, statusCode, trailers, opaque, context } = await request(`${DeviceURL}/waterLeakDetectors/${this.device.deviceID}`, { + method: 'GET', + query: { + 'locationId': this.locationId, + 'apikey': this.config.credentials?.consumerKey, + }, + headers: { + 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, + 'Content-Type': 'application/json', + }, + }); + const action = 'pushChanges'; + await this.statusCode(statusCode, action); + this.log.debug(`(pushChanges) trailers: ${JSON.stringify(trailers)}`); + this.log.debug(`(pushChanges) opaque: ${JSON.stringify(opaque)}`); + this.log.debug(`(pushChanges) context: ${JSON.stringify(context)}`); + const device: any = await body.json(); + this.log.debug(`(refreshStatus) ${device.deviceClass}: ${JSON.stringify(device)}`); this.device = device; this.batteryRemaining = Number(device.batteryRemaining); this.waterPresent = device.waterPresent; this.humidity = device.currentSensorReadings.humidity; this.hasDeviceCheckedIn = device.hasDeviceCheckedIn; this.temperature = device.currentSensorReadings.temperature; - this.debugLog(`Leak Sensor: ${this.accessory.displayName} device: ${superStringify(this.device)}`); + this.log.debug(`Leak Sensor: ${this.accessory.displayName} device: ${JSON.stringify(this.device)}`); this.parseStatus(); this.updateHomeKitCharacteristics(); } catch (e: any) { @@ -236,57 +263,57 @@ export class LeakSensor { */ async updateHomeKitCharacteristics(): Promise { if (this.BatteryLevel === undefined) { - this.debugLog(`Leak Sensor: ${this.accessory.displayName} BatteryLevel: ${this.BatteryLevel}`); + this.log.debug(`Leak Sensor: ${this.accessory.displayName} BatteryLevel: ${this.BatteryLevel}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.BatteryLevel, this.BatteryLevel); - this.debugLog(`Leak Sensor: ${this.accessory.displayName} updateCharacteristic BatteryLevel: ${this.BatteryLevel}`); + this.service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, this.BatteryLevel); + this.log.debug(`Leak Sensor: ${this.accessory.displayName} updateCharacteristic BatteryLevel: ${this.BatteryLevel}`); } if (this.StatusLowBattery === undefined) { - this.debugLog(`Leak Sensor: ${this.accessory.displayName} StatusLowBattery: ${this.StatusLowBattery}`); + this.log.debug(`Leak Sensor: ${this.accessory.displayName} StatusLowBattery: ${this.StatusLowBattery}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, this.StatusLowBattery); - this.debugLog(`Leak Sensor: ${this.accessory.displayName} updateCharacteristic StatusLowBattery: ${this.StatusLowBattery}`); + this.service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, this.StatusLowBattery); + this.log.debug(`Leak Sensor: ${this.accessory.displayName} updateCharacteristic StatusLowBattery: ${this.StatusLowBattery}`); } if (!this.device.leaksensor?.hide_leak) { if (this.LeakDetected === undefined) { - this.debugLog(`Leak Sensor: ${this.accessory.displayName} LeakDetected: ${this.LeakDetected}`); + this.log.debug(`Leak Sensor: ${this.accessory.displayName} LeakDetected: ${this.LeakDetected}`); } else { - this.leakService?.updateCharacteristic(this.platform.Characteristic.LeakDetected, this.LeakDetected); - this.debugLog(`Leak Sensor: ${this.accessory.displayName} updateCharacteristic LeakDetected: ${this.LeakDetected}`); + this.leakService?.updateCharacteristic(this.hap.Characteristic.LeakDetected, this.LeakDetected); + this.log.debug(`Leak Sensor: ${this.accessory.displayName} updateCharacteristic LeakDetected: ${this.LeakDetected}`); } if (this.StatusActive === undefined) { - this.debugLog(`Leak Sensor: ${this.accessory.displayName} StatusActive: ${this.StatusActive}`); + this.log.debug(`Leak Sensor: ${this.accessory.displayName} StatusActive: ${this.StatusActive}`); } else { - this.leakService?.updateCharacteristic(this.platform.Characteristic.StatusActive, this.StatusActive); - this.debugLog(`Leak Sensor: ${this.accessory.displayName} updateCharacteristic StatusActive: ${this.StatusActive}`); + this.leakService?.updateCharacteristic(this.hap.Characteristic.StatusActive, this.StatusActive); + this.log.debug(`Leak Sensor: ${this.accessory.displayName} updateCharacteristic StatusActive: ${this.StatusActive}`); } } if (this.device.leaksensor?.hide_temperature || this.CurrentTemperature === undefined) { if (!this.device.leaksensor?.hide_temperature) { - this.debugLog(`Leak Sensor: ${this.accessory.displayName} CurrentTemperature: ${this.CurrentTemperature}`); + this.log.debug(`Leak Sensor: ${this.accessory.displayName} CurrentTemperature: ${this.CurrentTemperature}`); } } else { - this.temperatureService?.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.CurrentTemperature); - this.debugLog(`Leak Sensor: ${this.accessory.displayName} updateCharacteristic CurrentTemperature: ${this.CurrentTemperature}`); + this.temperatureService?.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, this.CurrentTemperature); + this.log.debug(`Leak Sensor: ${this.accessory.displayName} updateCharacteristic CurrentTemperature: ${this.CurrentTemperature}`); } if (this.device.leaksensor?.hide_humidity || this.CurrentRelativeHumidity === undefined) { if (!this.device.leaksensor?.hide_humidity) { - this.debugLog(`Leak Sensor: ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`); + this.log.debug(`Leak Sensor: ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`); } } else { - this.humidityService?.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, this.CurrentRelativeHumidity); - this.debugLog(`Leak Sensor: ${this.accessory.displayName}` + ` updateCharacteristic CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`); + this.humidityService?.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, this.CurrentRelativeHumidity); + this.log.debug(`Leak Sensor: ${this.accessory.displayName}` + ` updateCharacteristic CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`); } } async apiError(e: any): Promise { - this.service.updateCharacteristic(this.platform.Characteristic.BatteryLevel, e); - this.service.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, e); + this.service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e); + this.service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e); if (!this.device.leaksensor?.hide_leak) { - this.leakService?.updateCharacteristic(this.platform.Characteristic.LeakDetected, e); - this.leakService?.updateCharacteristic(this.platform.Characteristic.StatusActive, e); + this.leakService?.updateCharacteristic(this.hap.Characteristic.LeakDetected, e); + this.leakService?.updateCharacteristic(this.hap.Characteristic.StatusActive, e); } - //throw new this.platform.api.hap.HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE); + //throw new this.api.hap.HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE); } async resideoAPIError(e: any): Promise { @@ -302,82 +329,85 @@ export class LeakSensor { } } if (e.message.includes('400')) { - this.platform.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Bad Request`); - this.debugLog('The client has issued an invalid request. This is commonly used to specify validation errors in a request payload.'); + this.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Bad Request`); + this.log.debug('The client has issued an invalid request. This is commonly used to specify validation errors in a request payload.'); } else if (e.message.includes('401')) { - this.platform.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Unauthorized Request`); - this.debugLog('Authorization for the API is required, but the request has not been authenticated.'); + this.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Unauthorized Request`); + this.log.debug('Authorization for the API is required, but the request has not been authenticated.'); } else if (e.message.includes('403')) { - this.platform.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Forbidden Request`); - this.debugLog('The request has been authenticated but does not have appropriate permissions, or a requested resource is not found.'); + this.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Forbidden Request`); + this.log.debug('The request has been authenticated but does not have appropriate permissions, or a requested resource is not found.'); } else if (e.message.includes('404')) { - this.platform.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Requst Not Found`); - this.debugLog('Specifies the requested path does not exist.'); + this.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Requst Not Found`); + this.log.debug('Specifies the requested path does not exist.'); } else if (e.message.includes('406')) { - this.platform.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Request Not Acceptable`); - this.debugLog('The client has requested a MIME type via the Accept header for a value not supported by the server.'); + this.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Request Not Acceptable`); + this.log.debug('The client has requested a MIME type via the Accept header for a value not supported by the server.'); } else if (e.message.includes('415')) { - this.platform.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Unsupported Requst Header`); - this.debugLog('The client has defined a contentType header that is not supported by the server.'); + this.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Unsupported Requst Header`); + this.log.debug('The client has defined a contentType header that is not supported by the server.'); } else if (e.message.includes('422')) { - this.platform.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Unprocessable Entity`); - this.debugLog( + this.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Unprocessable Entity`); + this.log.debug( 'The client has made a valid request, but the server cannot process it.' + - ' This is often used for APIs for which certain limits have been exceeded.', + ' This is often used for APIs for which certain limits have been exceeded.', ); } else if (e.message.includes('429')) { - this.platform.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Too Many Requests`); - this.debugLog('The client has exceeded the number of requests allowed for a given time window.'); + this.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Too Many Requests`); + this.log.debug('The client has exceeded the number of requests allowed for a given time window.'); } else if (e.message.includes('500')) { - this.platform.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Internal Server Error`); - this.debugLog('An unexpected error on the SmartThings servers has occurred. These errors should be rare.'); + this.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action}, Internal Server Error`); + this.log.debug('An unexpected error on the SmartThings servers has occurred. These errors should be rare.'); } else { - this.platform.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action},`); + this.log.error(`Leak Sensor: ${this.accessory.displayName} failed to ${this.action},`); } if (this.deviceLogging.includes('debug')) { - this.platform.log.error(`Leak Sensor: ${this.accessory.displayName} failed to pushChanges, Error Message: ${superStringify(e.message)}`); + this.log.error(`Leak Sensor: ${this.accessory.displayName} failed to pushChanges, Error Message: ${JSON.stringify(e.message)}`); } } - async config(device: settings.device & settings.devicesConfig): Promise { - let config = {}; - if (device.leaksensor) { - config = device.leaksensor; - } - if (device.logging !== undefined) { - config['logging'] = device.logging; - } - if (device.refreshRate !== undefined) { - config['refreshRate'] = device.refreshRate; - } - if (Object.entries(config).length !== 0) { - this.infoLog(`Leak Sensor: ${this.accessory.displayName} Config: ${superStringify(config)}`); + async statusCode(statusCode: number, action: string): Promise { + switch (statusCode) { + case 200: + this.log.debug(`${this.device.deviceClass}: ${this.accessory.displayName} Standard Response, statusCode: ${statusCode}, Action: ${action}`); + break; + case 400: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Bad Request, statusCode: ${statusCode}, Action: ${action}`); + break; + case 401: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Unauthorized, statusCode: ${statusCode}, Action: ${action}`); + break; + case 404: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Not Found, statusCode: ${statusCode}, Action: ${action}`); + break; + case 429: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Too Many Requests, statusCode: ${statusCode}, Action: ${action}`); + break; + case 500: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Internal Server Error (Meater Server), statusCode: ${statusCode}, ` + + `Action: ${action}`); + break; + default: + this.log.info(`${this.device.deviceClass}: ${this.accessory.displayName} Unknown statusCode: ${statusCode}, ` + + `Action: ${action}, Report Bugs Here: https://bit.ly/homebridge-resideo-bug-report`); } } - async refreshRate(device: settings.device & settings.devicesConfig): Promise { - if (device.refreshRate) { - this.deviceRefreshRate = this.accessory.context.refreshRate = device.refreshRate; - this.debugLog(`Leak Sensor: ${this.accessory.displayName} Using Device Config refreshRate: ${this.deviceRefreshRate}`); - } else if (this.platform.config.options!.refreshRate) { - this.deviceRefreshRate = this.accessory.context.refreshRate = this.platform.config.options!.refreshRate; - this.debugLog(`Leak Sensor: ${this.accessory.displayName} Using Platform Config refreshRate: ${this.deviceRefreshRate}`); - } - } - - async logs(device: settings.device & settings.devicesConfig): Promise { - if (this.platform.debugMode) { - this.deviceLogging = this.accessory.context.logging = 'debugMode'; - this.debugLog(`Leak Sensor: ${this.accessory.displayName} Using Debug Mode Logging: ${this.deviceLogging}`); - } else if (device.logging) { - this.deviceLogging = this.accessory.context.logging = device.logging; - this.debugLog(`Leak Sensor: ${this.accessory.displayName} Using Device Config Logging: ${this.deviceLogging}`); - } else if (this.platform.config.options?.logging) { - this.deviceLogging = this.accessory.context.logging = this.platform.config.options?.logging; - this.debugLog(`Leak Sensor: ${this.accessory.displayName} Using Platform Config Logging: ${this.deviceLogging}`); + async deviceLogs() { + this.debugMode = process.argv.includes('-D') || process.argv.includes('--debug'); + this.deviceLogging = this.device.logging || this.config.options?.logging || 'standard'; + if (this.debugMode) { + this.deviceLogging = 'debugMode'; + this.debugLog(`${this.constructor.name}: ${this.accessory.displayName} Using Debug Mode Logging: ${this.deviceLogging}`); + } else if (this.device.logging) { + this.deviceLogging = this.device.logging; + this.debugLog(`${this.constructor.name}: ${this.accessory.displayName} Using Device Config Logging: ${this.deviceLogging}`); + } else if (this.config.options?.logging) { + this.deviceLogging = this.config.options?.logging; + this.debugLog(`${this.constructor.name}: ${this.accessory.displayName} Using Platform Config Logging: ${this.deviceLogging}`); } else { - this.deviceLogging = this.accessory.context.logging = 'standard'; - this.debugLog(`Leak Sensor: ${this.accessory.displayName} Logging Not Set, Using: ${this.deviceLogging}`); + this.deviceLogging = 'standard'; + this.debugLog(`${this.constructor.name}: ${this.accessory.displayName} Logging Not Set, Using: ${this.deviceLogging}`); } } @@ -396,7 +426,7 @@ export class LeakSensor { } } - debugWarnLog(...log: any[]): void { + debugWarnLog({ log = [] }: { log?: any[]; } = {}): void { if (this.enablingDeviceLogging()) { if (this.deviceLogging?.includes('debug')) { this.platform.log.warn('[DEBUG]', String(...log)); @@ -420,11 +450,16 @@ export class LeakSensor { debugLog(...log: any[]): void { if (this.enablingDeviceLogging()) { - if (this.deviceLogging === 'debug') { + if (this.deviceLogging === 'debugMode') { + this.log.debug('[HOMEBRIDGE DEBUGMODE]', String(...log)); + } else if (this.deviceLogging === 'debug') { + this.log.info('[DEBUG]', String(...log)); + } + /*if (this.deviceLogging === 'debug') { this.platform.log.info('[DEBUG]', String(...log)); } else { this.platform.log.debug(String(...log)); - } + }*/ } } diff --git a/src/devices/roomsensors.ts b/src/devices/roomsensors.ts index 94982fbf..6fd6ca74 100644 --- a/src/devices/roomsensors.ts +++ b/src/devices/roomsensors.ts @@ -1,9 +1,8 @@ -import { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; -import { interval, Subject } from 'rxjs'; -import superStringify from 'super-stringify'; -import { skipWhile, take } from 'rxjs/operators'; -import { ResideoPlatform } from '../platform'; -import * as settings from '../settings'; +import { Subject, interval } from 'rxjs'; +import { take, skipWhile } from 'rxjs/operators'; +import { ResideoPlatform } from '../platform.js'; +import { Service, PlatformAccessory, CharacteristicValue, API, HAP, Logging } from 'homebridge'; +import { devicesConfig, location, resideoDevice, ResideoPlatformConfig, sensorAccessory, T9groups } from '../settings.js'; /** * Platform Accessory @@ -11,6 +10,10 @@ import * as settings from '../settings'; * Each accessory may expose multiple services of different service types. */ export class RoomSensors { + public readonly api: API; + public readonly log: Logging; + public readonly config!: ResideoPlatformConfig; + protected readonly hap: HAP; // Services service: Service; temperatureService?: Service; @@ -39,22 +42,27 @@ export class RoomSensors { constructor( private readonly platform: ResideoPlatform, - private accessory: PlatformAccessory, - public readonly locationId: settings.location['locationID'], - public device: settings.device & settings.devicesConfig, - public sensorAccessory: settings.sensorAccessory, - public readonly group: settings.T9groups, + private readonly accessory: PlatformAccessory, + public readonly locationId: location['locationID'], + public device: resideoDevice & devicesConfig, + public sensorAccessory: sensorAccessory, + public readonly group: T9groups, ) { - this.logs(device); - this.refreshRate(device); - this.config(device); - // default placeholders - this.CurrentTemperature; - this.StatusLowBattery; - this.OccupancyDetected; - this.CurrentRelativeHumidity; + this.api = this.platform.api; + this.log = this.platform.log; + this.config = this.platform.config; + this.hap = this.api.hap; + + this.StatusLowBattery = this.accessory.context.StatusLowBattery || this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; + this.OccupancyDetected = this.accessory.context.OccupancyDetected || this.hap.Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED; + this.CurrentTemperature = this.accessory.context.CurrentTemperature || 20; + this.CurrentRelativeHumidity = this.accessory.context.CurrentRelativeHumidity || 50; + this.TemperatureDisplayUnits = this.accessory.context.TemperatureDisplayUnits || this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS; this.accessoryId = sensorAccessory.accessoryId; this.roomId = sensorAccessory.roomId; + accessory.context.FirmwareRevision = 'v2.0.0'; + + this.deviceLogging = this.device.logging || this.config.options?.logging || 'standard'; // this is subject we use to track when we need to POST changes to the Resideo API this.doSensorUpdate = new Subject(); @@ -62,26 +70,24 @@ export class RoomSensors { // set accessory information accessory - .getService(this.platform.Service.AccessoryInformation)! - .setCharacteristic(this.platform.Characteristic.Manufacturer, 'Resideo') - .setCharacteristic(this.platform.Characteristic.Model, sensorAccessory.accessoryAttribute.model) - .setCharacteristic(this.platform.Characteristic.SerialNumber, sensorAccessory.deviceID) - .setCharacteristic(this.platform.Characteristic.FirmwareRevision, accessory.context.firmwareRevision) - .getCharacteristic(this.platform.Characteristic.FirmwareRevision) - .updateValue(accessory.context.firmwareRevision); + .getService(this.hap.Service.AccessoryInformation)! + .setCharacteristic(this.hap.Characteristic.Manufacturer, 'Resideo') + .setCharacteristic(this.hap.Characteristic.Model, sensorAccessory.accessoryAttribute.model) + .setCharacteristic(this.hap.Characteristic.SerialNumber, sensorAccessory.deviceID) + .setCharacteristic(this.hap.Characteristic.FirmwareRevision, accessory.context.firmwareRevision || 'v2.0.0'); // get the BatteryService service if it exists, otherwise create a new Battery service // you can create multiple services for each accessory - (this.service = this.accessory.getService(this.platform.Service.Battery) || this.accessory.addService(this.platform.Service.Battery)), + (this.service = this.accessory.getService(this.hap.Service.Battery) || this.accessory.addService(this.hap.Service.Battery)), `${accessory.displayName} Battery`; // To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error, // when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id: - // this.accessory.getService('NAME') ?? this.accessory.addService(this.platform.Service.Battery, 'NAME', 'USER_DEFINED_SUBTYPE'); + // this.accessory.getService('NAME') ?? this.accessory.addService(this.hap.Service.Battery, 'NAME', 'USER_DEFINED_SUBTYPE'); // set the service name, this is what is displayed as the default name on the Home app // in this example we are using the name we stored in the `accessory.context` in the `discoverDevices` method. - this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.displayName); + this.service.setCharacteristic(this.hap.Characteristic.Name, accessory.displayName); // each service must implement at-minimum the "required characteristics" for the given service type // see https://developers.homebridge.io/#/service/ @@ -90,23 +96,23 @@ export class RoomSensors { this.parseStatus(); // Set Charging State - this.service.setCharacteristic(this.platform.Characteristic.ChargingState, 2); + this.service.setCharacteristic(this.hap.Characteristic.ChargingState, 2); // Temperature Sensor Service if (device.thermostat?.roomsensor?.hide_temperature) { - this.debugLog(`Room Sensor: ${accessory.displayName} Removing Temperature Sensor Service`); - this.temperatureService = this.accessory.getService(this.platform.Service.TemperatureSensor); + this.log.debug(`Room Sensor: ${accessory.displayName} Removing Temperature Sensor Service`); + this.temperatureService = this.accessory.getService(this.hap.Service.TemperatureSensor); accessory.removeService(this.temperatureService!); } else if (!this.temperatureService) { - this.debugLog(`Room Sensor: ${accessory.displayName} Add Temperature Sensor Service`); + this.log.debug(`Room Sensor: ${accessory.displayName} Add Temperature Sensor Service`); (this.temperatureService = - this.accessory.getService(this.platform.Service.TemperatureSensor) || this.accessory.addService(this.platform.Service.TemperatureSensor)), + this.accessory.getService(this.hap.Service.TemperatureSensor) || this.accessory.addService(this.hap.Service.TemperatureSensor)), `${accessory.displayName} Temperature Sensor`; - this.temperatureService.setCharacteristic(this.platform.Characteristic.Name, `${accessory.displayName} Temperature Sensor`); + this.temperatureService.setCharacteristic(this.hap.Characteristic.Name, `${accessory.displayName} Temperature Sensor`); this.temperatureService - .getCharacteristic(this.platform.Characteristic.CurrentTemperature) + .getCharacteristic(this.hap.Characteristic.CurrentTemperature) .setProps({ minValue: -273.15, maxValue: 100, @@ -116,40 +122,40 @@ export class RoomSensors { return this.CurrentTemperature; }); } else { - this.debugLog(`Room Sensor: ${accessory.displayName} Temperature Sensor Service Not Added`); + this.log.debug(`Room Sensor: ${accessory.displayName} Temperature Sensor Service Not Added`); } // Occupancy Sensor Service if (device.thermostat?.roomsensor?.hide_occupancy) { - this.debugLog(`Room Sensor: ${accessory.displayName} Removing Occupancy Sensor Service`); - this.occupancyService = this.accessory.getService(this.platform.Service.OccupancySensor); + this.log.debug(`Room Sensor: ${accessory.displayName} Removing Occupancy Sensor Service`); + this.occupancyService = this.accessory.getService(this.hap.Service.OccupancySensor); accessory.removeService(this.occupancyService!); } else if (!this.occupancyService) { - this.debugLog(`Room Sensor: ${accessory.displayName} Add Occupancy Sensor Service`); + this.log.debug(`Room Sensor: ${accessory.displayName} Add Occupancy Sensor Service`); (this.occupancyService = - this.accessory.getService(this.platform.Service.OccupancySensor) || this.accessory.addService(this.platform.Service.OccupancySensor)), + this.accessory.getService(this.hap.Service.OccupancySensor) || this.accessory.addService(this.hap.Service.OccupancySensor)), `${accessory.displayName} Occupancy Sensor`; - this.occupancyService.setCharacteristic(this.platform.Characteristic.Name, `${accessory.displayName} Occupancy Sensor`); + this.occupancyService.setCharacteristic(this.hap.Characteristic.Name, `${accessory.displayName} Occupancy Sensor`); } else { - this.debugLog(`Room Sensor: ${accessory.displayName} Occupancy Sensor Service Not Added`); + this.log.debug(`Room Sensor: ${accessory.displayName} Occupancy Sensor Service Not Added`); } // Humidity Sensor Service if (device.thermostat?.roomsensor?.hide_humidity) { - this.debugLog(`Room Sensor: ${accessory.displayName} Removing Humidity Sensor Service`); - this.humidityService = this.accessory.getService(this.platform.Service.HumiditySensor); + this.log.debug(`Room Sensor: ${accessory.displayName} Removing Humidity Sensor Service`); + this.humidityService = this.accessory.getService(this.hap.Service.HumiditySensor); accessory.removeService(this.humidityService!); } else if (!this.humidityService) { - this.debugLog(`Room Sensor: ${accessory.displayName} Add Humidity Sensor Service`); + this.log.debug(`Room Sensor: ${accessory.displayName} Add Humidity Sensor Service`); (this.humidityService = - this.accessory.getService(this.platform.Service.HumiditySensor) || this.accessory.addService(this.platform.Service.HumiditySensor)), + this.accessory.getService(this.hap.Service.HumiditySensor) || this.accessory.addService(this.hap.Service.HumiditySensor)), `${accessory.displayName} Humidity Sensor`; - this.humidityService.setCharacteristic(this.platform.Characteristic.Name, `${accessory.displayName} Humidity Sensor`); + this.humidityService.setCharacteristic(this.hap.Characteristic.Name, `${accessory.displayName} Humidity Sensor`); this.humidityService - .getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity) + .getCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity) .setProps({ minStep: 0.1, }) @@ -157,14 +163,14 @@ export class RoomSensors { return this.CurrentRelativeHumidity; }); } else { - this.debugLog(`Room Sensor: ${accessory.displayName} Humidity Sensor Service Not Added`); + this.log.debug(`Room Sensor: ${accessory.displayName} Humidity Sensor Service Not Added`); } // Retrieve initial values and updateHomekit this.updateHomeKitCharacteristics(); // Start an update interval - interval(this.platform.config.options!.refreshRate! * 1000) + interval(this.config.options!.refreshRate! * 1000) .pipe(skipWhile(() => this.SensorUpdateInProgress)) .subscribe(async () => { await this.refreshStatus(); @@ -177,17 +183,17 @@ export class RoomSensors { async parseStatus(): Promise { // Set Room Sensor State if (this.sensorAccessory.accessoryValue.batteryStatus.startsWith('Ok')) { - this.StatusLowBattery = this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; + this.StatusLowBattery = this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; } else { - this.StatusLowBattery = this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW; + this.StatusLowBattery = this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW; } - this.debugLog(`Room Sensor: ${this.accessory.displayName} StatusLowBattery: ${this.StatusLowBattery}`); + this.log.debug(`Room Sensor: ${this.accessory.displayName} StatusLowBattery: ${this.StatusLowBattery}`); // Set Temperature Sensor State if (!this.device.thermostat?.roomsensor?.hide_temperature) { this.CurrentTemperature = this.toCelsius(this.sensorAccessory.accessoryValue.indoorTemperature); } - this.debugLog(`Room Sensor: ${this.accessory.displayName} CurrentTemperature: ${this.CurrentTemperature}°c`); + this.log.debug(`Room Sensor: ${this.accessory.displayName} CurrentTemperature: ${this.CurrentTemperature}°c`); // Set Occupancy Sensor State if (!this.device.thermostat?.roomsensor?.hide_occupancy) { @@ -202,7 +208,7 @@ export class RoomSensors { if (!this.device.thermostat?.roomsensor?.hide_humidity) { this.CurrentRelativeHumidity = this.sensorAccessory.accessoryValue.indoorHumidity; } - this.debugLog(`Room Sensor: ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}%`); + this.log.debug(`Room Sensor: ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}%`); } /** @@ -226,37 +232,37 @@ export class RoomSensors { */ async updateHomeKitCharacteristics(): Promise { if (this.StatusLowBattery === undefined) { - this.debugLog(`Room Sensor: ${this.accessory.displayName} StatusLowBattery: ${this.StatusLowBattery}`); + this.log.debug(`Room Sensor: ${this.accessory.displayName} StatusLowBattery: ${this.StatusLowBattery}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, this.StatusLowBattery); - this.debugLog(`Room Sensor: ${this.accessory.displayName} updateCharacteristic StatusLowBattery: ${this.StatusLowBattery}`); + this.service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, this.StatusLowBattery); + this.log.debug(`Room Sensor: ${this.accessory.displayName} updateCharacteristic StatusLowBattery: ${this.StatusLowBattery}`); } if (this.device.thermostat?.roomsensor?.hide_temperature || (this.CurrentTemperature === undefined && Number.isNaN(this.CurrentTemperature))) { - this.debugLog(`Room Sensor: ${this.accessory.displayName} CurrentTemperature: ${this.CurrentTemperature}`); + this.log.debug(`Room Sensor: ${this.accessory.displayName} CurrentTemperature: ${this.CurrentTemperature}`); } else { - this.temperatureService?.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.CurrentTemperature); - this.debugLog(`Room Sensor: ${this.accessory.displayName} updateCharacteristic CurrentTemperature: ${this.CurrentTemperature}`); + this.temperatureService?.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, this.CurrentTemperature); + this.log.debug(`Room Sensor: ${this.accessory.displayName} updateCharacteristic CurrentTemperature: ${this.CurrentTemperature}`); } if (this.device.thermostat?.roomsensor?.hide_occupancy || this.OccupancyDetected === undefined) { - this.debugLog(`Room Sensor: ${this.accessory.displayName} OccupancyDetected: ${this.OccupancyDetected}`); + this.log.debug(`Room Sensor: ${this.accessory.displayName} OccupancyDetected: ${this.OccupancyDetected}`); } else { - this.occupancyService?.updateCharacteristic(this.platform.Characteristic.OccupancyDetected, this.OccupancyDetected); - this.debugLog(`Room Sensor: ${this.accessory.displayName} updateCharacteristic OccupancyDetected: ${this.OccupancyDetected}`); + this.occupancyService?.updateCharacteristic(this.hap.Characteristic.OccupancyDetected, this.OccupancyDetected); + this.log.debug(`Room Sensor: ${this.accessory.displayName} updateCharacteristic OccupancyDetected: ${this.OccupancyDetected}`); } if (this.device.thermostat?.roomsensor?.hide_humidity || this.CurrentRelativeHumidity === undefined) { - this.debugLog(`Room Sensor: ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`); + this.log.debug(`Room Sensor: ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`); } else { - this.humidityService?.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, this.CurrentRelativeHumidity); - this.debugLog(`Room Sensor: ${this.accessory.displayName}` + ` updateCharacteristic CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`); + this.humidityService?.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, this.CurrentRelativeHumidity); + this.log.debug(`Room Sensor: ${this.accessory.displayName}` + ` updateCharacteristic CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`); } } async apiError(e: any): Promise { - this.service.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, e); + this.service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e); if (!this.device.thermostat?.roomsensor?.hide_temperature) { - this.temperatureService?.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, e); + this.temperatureService?.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e); } - //throw new this.platform.api.hap.HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE); + //throw new this.api.hap.HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE); } async resideoAPIError(e: any): Promise { @@ -272,40 +278,66 @@ export class RoomSensors { } } if (e.message.includes('400')) { - this.platform.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Bad Request`); - this.debugLog('The client has issued an invalid request. This is commonly used to specify validation errors in a request payload.'); + this.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Bad Request`); + this.log.debug('The client has issued an invalid request. This is commonly used to specify validation errors in a request payload.'); } else if (e.message.includes('401')) { - this.platform.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Unauthorized Request`); - this.debugLog('Authorization for the API is required, but the request has not been authenticated.'); + this.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Unauthorized Request`); + this.log.debug('Authorization for the API is required, but the request has not been authenticated.'); } else if (e.message.includes('403')) { - this.platform.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Forbidden Request`); - this.debugLog('The request has been authenticated but does not have appropriate permissions, or a requested resource is not found.'); + this.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Forbidden Request`); + this.log.debug('The request has been authenticated but does not have appropriate permissions, or a requested resource is not found.'); } else if (e.message.includes('404')) { - this.platform.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Requst Not Found`); - this.debugLog('Specifies the requested path does not exist.'); + this.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Requst Not Found`); + this.log.debug('Specifies the requested path does not exist.'); } else if (e.message.includes('406')) { - this.platform.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Request Not Acceptable`); - this.debugLog('The client has requested a MIME type via the Accept header for a value not supported by the server.'); + this.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Request Not Acceptable`); + this.log.debug('The client has requested a MIME type via the Accept header for a value not supported by the server.'); } else if (e.message.includes('415')) { - this.platform.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Unsupported Requst Header`); - this.debugLog('The client has defined a contentType header that is not supported by the server.'); + this.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Unsupported Requst Header`); + this.log.debug('The client has defined a contentType header that is not supported by the server.'); } else if (e.message.includes('422')) { - this.platform.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Unprocessable Entity`); - this.debugLog( + this.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Unprocessable Entity`); + this.log.debug( 'The client has made a valid request, but the server cannot process it.' + ' This is often used for APIs for which certain limits have been exceeded.', ); } else if (e.message.includes('429')) { - this.platform.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Too Many Requests`); - this.debugLog('The client has exceeded the number of requests allowed for a given time window.'); + this.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Too Many Requests`); + this.log.debug('The client has exceeded the number of requests allowed for a given time window.'); } else if (e.message.includes('500')) { - this.platform.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Internal Server Error`); - this.debugLog('An unexpected error on the SmartThings servers has occurred. These errors should be rare.'); + this.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action}, Internal Server Error`); + this.log.debug('An unexpected error on the SmartThings servers has occurred. These errors should be rare.'); } else { - this.platform.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action},`); + this.log.error(`Room Sensor: ${this.accessory.displayName} failed to ${this.action},`); } if (this.deviceLogging.includes('debug')) { - this.platform.log.error(`Room Sensor: ${this.accessory.displayName} failed to pushChanges, Error Message: ${superStringify(e.message)}`); + this.log.error(`Room Sensor: ${this.accessory.displayName} failed to pushChanges, Error Message: ${JSON.stringify(e.message)}`); + } + } + + async statusCode(statusCode: number): Promise { + switch (statusCode) { + case 200: + this.log.debug(`${this.accessory.displayName} Standard Response, statusCode: ${statusCode}`); + break; + case 400: + this.log.error(`${this.accessory.displayName} Bad Request, statusCode: ${statusCode}`); + break; + case 401: + this.log.error(`${this.accessory.displayName} Unauthorized, statusCode: ${statusCode}`); + break; + case 404: + this.log.error(`${this.accessory.displayName} Not Found, statusCode: ${statusCode}`); + break; + case 429: + this.log.error(`${this.accessory.displayName} Too Many Requests, statusCode: ${statusCode}`); + break; + case 500: + this.log.error(`${this.accessory.displayName} Internal Server Error (Meater Server), statusCode: ${statusCode}`); + break; + default: + this.log.info( + `${this.accessory.displayName} Unknown statusCode: ${statusCode}, Report Bugs Here: https://bit.ly/homebridge-resideo-bug-report`); } } @@ -313,7 +345,7 @@ export class RoomSensors { * Converts the value to celsius if the temperature units are in Fahrenheit */ toCelsius(value: number): number { - if (this.TemperatureDisplayUnits === this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS) { + if (this.TemperatureDisplayUnits === this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS) { return value; } @@ -321,51 +353,6 @@ export class RoomSensors { return Math.round((5 / 9) * (value - 32) * 2) / 2; } - async config(device: settings.device & settings.devicesConfig): Promise { - let config = {}; - if (device.thermostat?.roomsensor) { - config = device.thermostat?.roomsensor; - } - if (device.logging !== undefined) { - config['logging'] = device.thermostat?.roomsensor?.logging; - } - if (device.refreshRate !== undefined) { - config['refreshRate'] = device.thermostat?.roomsensor?.refreshRate; - } - if (Object.entries(config).length !== 0) { - this.infoLog(`Room Sensor: ${this.accessory.displayName} Config: ${superStringify(config)}`); - } - } - - async refreshRate(device: settings.device & settings.devicesConfig): Promise { - if (device.thermostat?.roomsensor?.refreshRate) { - this.deviceRefreshRate = this.accessory.context.refreshRate = device.thermostat?.roomsensor?.refreshRate; - this.debugLog(`Room Sensor: ${this.accessory.displayName} Using Device Config refreshRate: ${this.deviceRefreshRate}`); - } else if (device.refreshRate) { - this.deviceRefreshRate = this.accessory.context.refreshRate = device.refreshRate; - this.debugLog(`Room Sensor: ${this.accessory.displayName} Using Thermostat Device Config refreshRate: ${this.deviceRefreshRate}`); - } else if (this.platform.config.options!.refreshRate) { - this.deviceRefreshRate = this.accessory.context.refreshRate = this.platform.config.options!.refreshRate; - this.debugLog(`Room Sensor: ${this.accessory.displayName} Using Platform Config refreshRate: ${this.deviceRefreshRate}`); - } - } - - async logs(device: settings.device & settings.devicesConfig): Promise { - if (this.platform.debugMode) { - this.deviceLogging = this.accessory.context.logging = 'debugMode'; - this.debugLog(`Room Sensor: ${this.accessory.displayName} Using Debug Mode Logging: ${this.deviceLogging}`); - } else if (device.thermostat?.roomsensor?.logging) { - this.deviceLogging = this.accessory.context.logging = device.thermostat?.roomsensor?.logging; - this.debugLog(`Room Sensor: ${this.accessory.displayName} Using Device Config Logging: ${this.deviceLogging}`); - } else if (this.platform.config.options?.logging) { - this.deviceLogging = this.accessory.context.logging = this.platform.config.options?.logging; - this.debugLog(`Room Sensor: ${this.accessory.displayName} Using Platform Config Logging: ${this.deviceLogging}`); - } else { - this.deviceLogging = this.accessory.context.logging = 'standard'; - this.debugLog(`Room Sensor: ${this.accessory.displayName} Logging Not Set, Using: ${this.deviceLogging}`); - } - } - /** * Logging for Device */ @@ -381,7 +368,7 @@ export class RoomSensors { } } - debugWarnLog(...log: any[]): void { + debugWarnLog({ log = [] }: { log?: any[]; } = {}): void { if (this.enablingDeviceLogging()) { if (this.deviceLogging?.includes('debug')) { this.platform.log.warn('[DEBUG]', String(...log)); diff --git a/src/devices/roomsensorthermostats.ts b/src/devices/roomsensorthermostats.ts index 27df4a4d..aa9a6803 100644 --- a/src/devices/roomsensorthermostats.ts +++ b/src/devices/roomsensorthermostats.ts @@ -1,9 +1,11 @@ -import { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import { request } from 'undici'; import { interval, Subject } from 'rxjs'; -import superStringify from 'super-stringify'; +import { ResideoPlatform } from '../platform.js'; import { debounceTime, skipWhile, take, tap } from 'rxjs/operators'; -import { ResideoPlatform } from '../platform'; -import * as settings from '../settings'; +import { Service, PlatformAccessory, CharacteristicValue, API, HAP, Logging } from 'homebridge'; +import { + FanChangeableValues, devicesConfig, modes, resideoDevice, sensorAccessory, T9groups, location, DeviceURL, payload, ResideoPlatformConfig, +} from '../settings.js'; /** * Platform Accessory @@ -11,6 +13,10 @@ import * as settings from '../settings'; * Each accessory may expose multiple services of different service types. */ export class RoomSensorThermostat { + public readonly api: API; + public readonly log: Logging; + public readonly config!: ResideoPlatformConfig; + protected readonly hap: HAP; // Services service: Service; @@ -25,11 +31,11 @@ export class RoomSensorThermostat { HeatingThresholdTemperature!: CharacteristicValue; // Others - modes: settings.modes; + modes: modes; action!: string; roompriority: any; resideoMode!: Array; - deviceFan!: settings.FanChangeableValues; + deviceFan!: FanChangeableValues; // Config deviceLogging!: string; @@ -49,21 +55,35 @@ export class RoomSensorThermostat { constructor( private readonly platform: ResideoPlatform, - private accessory: PlatformAccessory, - public readonly locationId: settings.location['locationID'], - public device: settings.device & settings.devicesConfig, - public sensorAccessory: settings.sensorAccessory, - public readonly group: settings.T9groups, + private readonly accessory: PlatformAccessory, + public readonly locationId: location['locationID'], + public device: resideoDevice & devicesConfig, + public sensorAccessory: sensorAccessory, + public readonly group: T9groups, ) { - this.logs(device); - this.refreshRate(device); - this.config(device); + this.api = this.platform.api; + this.log = this.platform.log; + this.config = this.platform.config; + this.hap = this.api.hap; + + this.TargetTemperature = this.accessory.context.TargetTemperature || 20; + this.CurrentTemperature = this.accessory.context.CurrentTemperature || 20; + this.CurrentRelativeHumidity = this.accessory.context.CurrentRelativeHumidity || 50; + this.TemperatureDisplayUnits = this.accessory.context.TemperatureDisplayUnits || this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS; + this.TargetHeatingCoolingState = this.accessory.context.TargetHeatingCoolingState || this.hap.Characteristic.TargetHeatingCoolingState.AUTO; + this.CurrentHeatingCoolingState = this.accessory.context.CurrentHeatingCoolingState || this.hap.Characteristic.CurrentHeatingCoolingState.OFF; + this.CoolingThresholdTemperature = this.accessory.context.CoolingThresholdTemperature || 20; + this.HeatingThresholdTemperature = this.accessory.context.HeatingThresholdTemperature || 22; + accessory.context.FirmwareRevision = 'v2.0.0'; + + this.deviceLogging = this.device.logging || this.config.options?.logging || 'standard'; + // Map Resideo Modes to HomeKit Modes this.modes = { - Off: platform.Characteristic.TargetHeatingCoolingState.OFF, - Heat: platform.Characteristic.TargetHeatingCoolingState.HEAT, - Cool: platform.Characteristic.TargetHeatingCoolingState.COOL, - Auto: platform.Characteristic.TargetHeatingCoolingState.AUTO, + Off: this.hap.Characteristic.TargetHeatingCoolingState.OFF, + Heat: this.hap.Characteristic.TargetHeatingCoolingState.HEAT, + Cool: this.hap.Characteristic.TargetHeatingCoolingState.COOL, + Auto: this.hap.Characteristic.TargetHeatingCoolingState.AUTO, }; // Map HomeKit Modes to Resideo Modes @@ -88,26 +108,24 @@ export class RoomSensorThermostat { // set accessory information accessory - .getService(this.platform.Service.AccessoryInformation)! - .setCharacteristic(this.platform.Characteristic.Manufacturer, 'Resideo') - .setCharacteristic(this.platform.Characteristic.Model, sensorAccessory.accessoryAttribute.model || '1100') - .setCharacteristic(this.platform.Characteristic.SerialNumber, sensorAccessory.deviceID) - .setCharacteristic(this.platform.Characteristic.FirmwareRevision, accessory.context.firmwareRevision) - .getCharacteristic(this.platform.Characteristic.FirmwareRevision) - .updateValue(accessory.context.firmwareRevision); + .getService(this.hap.Service.AccessoryInformation)! + .setCharacteristic(this.hap.Characteristic.Manufacturer, 'Resideo') + .setCharacteristic(this.hap.Characteristic.Model, sensorAccessory.accessoryAttribute.model || '1100') + .setCharacteristic(this.hap.Characteristic.SerialNumber, sensorAccessory.deviceID) + .setCharacteristic(this.hap.Characteristic.FirmwareRevision, accessory.context.firmwareRevision || 'v2.0.0'); // get the LightBulb service if it exists, otherwise create a new LightBulb service // you can create multiple services for each accessory - (this.service = this.accessory.getService(this.platform.Service.Thermostat) || this.accessory.addService(this.platform.Service.Thermostat)), + (this.service = this.accessory.getService(this.hap.Service.Thermostat) || this.accessory.addService(this.hap.Service.Thermostat)), `${accessory.displayName} Thermostat`; // To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error, // when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id: - // this.accessory.getService('NAME') ?? this.accessory.addService(this.platform.Service.Lightbulb, 'NAME', 'USER_DEFINED_SUBTYPE'); + // this.accessory.getService('NAME') ?? this.accessory.addService(this.hap.Service.Lightbulb, 'NAME', 'USER_DEFINED_SUBTYPE'); // set the service name, this is what is displayed as the default name on the Home app // in this example we are using the name we stored in the `accessory.context` in the `discoverDevices` method. - this.service.setCharacteristic(this.platform.Characteristic.Name, `${accessory.displayName} Thermostat`); + this.service.setCharacteristic(this.hap.Characteristic.Name, `${accessory.displayName} Thermostat`); // each service must implement at-minimum the "required characteristics" for the given service type // see https://developers.homebridge.io/#/service/Thermostat @@ -117,9 +135,9 @@ export class RoomSensorThermostat { // Set Min and Max if (device.changeableValues!.heatCoolMode === 'Heat') { - this.debugLog(`Room Sensor Thermostat: ${accessory.displayName} mode: ${device.changeableValues!.heatCoolMode}`); + this.log.debug(`Room Sensor Thermostat: ${accessory.displayName} mode: ${device.changeableValues!.heatCoolMode}`); this.service - .getCharacteristic(this.platform.Characteristic.TargetTemperature) + .getCharacteristic(this.hap.Characteristic.TargetTemperature) .setProps({ minValue: this.toCelsius(device.minHeatSetpoint!), maxValue: this.toCelsius(device.maxHeatSetpoint!), @@ -129,9 +147,9 @@ export class RoomSensorThermostat { return this.TargetTemperature; }); } else { - this.debugLog(`Room Sensor Thermostat: ${accessory.displayName} mode: ${device.changeableValues!.heatCoolMode}`); + this.log.debug(`Room Sensor Thermostat: ${accessory.displayName} mode: ${device.changeableValues!.heatCoolMode}`); this.service - .getCharacteristic(this.platform.Characteristic.TargetTemperature) + .getCharacteristic(this.hap.Characteristic.TargetTemperature) .setProps({ minValue: this.toCelsius(device.minCoolSetpoint!), maxValue: this.toCelsius(device.maxCoolSetpoint!), @@ -147,27 +165,27 @@ export class RoomSensorThermostat { // Set control bindings const TargetState = this.TargetState(); this.service - .getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState) + .getCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState) .setProps({ validValues: TargetState, }) .onSet(this.setTargetHeatingCoolingState.bind(this)); - this.service.setCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState, this.CurrentHeatingCoolingState); + this.service.setCharacteristic(this.hap.Characteristic.CurrentHeatingCoolingState, this.CurrentHeatingCoolingState); - this.service.getCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature).onSet(this.setHeatingThresholdTemperature.bind(this)); + this.service.getCharacteristic(this.hap.Characteristic.HeatingThresholdTemperature).onSet(this.setHeatingThresholdTemperature.bind(this)); - this.service.getCharacteristic(this.platform.Characteristic.CoolingThresholdTemperature).onSet(this.setCoolingThresholdTemperature.bind(this)); + this.service.getCharacteristic(this.hap.Characteristic.CoolingThresholdTemperature).onSet(this.setCoolingThresholdTemperature.bind(this)); - this.service.getCharacteristic(this.platform.Characteristic.TargetTemperature).onSet(this.setTargetTemperature.bind(this)); + this.service.getCharacteristic(this.hap.Characteristic.TargetTemperature).onSet(this.setTargetTemperature.bind(this)); - this.service.getCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits).onSet(this.setTemperatureDisplayUnits.bind(this)); + this.service.getCharacteristic(this.hap.Characteristic.TemperatureDisplayUnits).onSet(this.setTemperatureDisplayUnits.bind(this)); // Retrieve initial values and updateHomekit this.updateHomeKitCharacteristics(); // Start an update interval - interval(this.platform.config.options!.refreshRate! * 1000) + interval(this.config.options!.refreshRate! * 1000) .pipe(skipWhile(() => this.thermostatUpdateInProgress)) .subscribe(async () => { await this.refreshStatus(); @@ -182,7 +200,7 @@ export class RoomSensorThermostat { tap(() => { this.roomUpdateInProgress = true; }), - debounceTime(this.platform.config.options!.pushRate! * 500), + debounceTime(this.config.options!.pushRate! * 500), ) .subscribe(async () => { try { @@ -214,7 +232,7 @@ export class RoomSensorThermostat { tap(() => { this.thermostatUpdateInProgress = true; }), - debounceTime(this.platform.config.options!.pushRate! * 1000), + debounceTime(this.config.options!.pushRate! * 1000), ) .subscribe(async () => { try { @@ -240,15 +258,11 @@ export class RoomSensorThermostat { */ async parseStatus(): Promise { if (this.device.units === 'Fahrenheit') { - this.TemperatureDisplayUnits = this.platform.Characteristic.TemperatureDisplayUnits.FAHRENHEIT; + this.TemperatureDisplayUnits = this.hap.Characteristic.TemperatureDisplayUnits.FAHRENHEIT; } if (this.device.units === 'Celsius') { - this.TemperatureDisplayUnits = this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS; + this.TemperatureDisplayUnits = this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS; } - /*this.TemperatureDisplayUnits = this.device.units === 'Fahrenheit' ? this.platform.Characteristic.TemperatureDisplayUnits.FAHRENHEIT : - this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS; - this.TemperatureDisplayUnits = this.device.units === 'Fahrenheit' ? this.platform.Characteristic.TemperatureDisplayUnits.FAHRENHEIT : - this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS;*/ this.CurrentTemperature = this.toCelsius(this.sensorAccessory.accessoryValue.indoorTemperature); this.CurrentRelativeHumidity = this.sensorAccessory.accessoryValue.indoorHumidity; @@ -277,10 +291,10 @@ export class RoomSensorThermostat { default: this.CurrentHeatingCoolingState = 0; } - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} CurrentHeatingCoolingState: ${this.CurrentHeatingCoolingState}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} CurrentHeatingCoolingState: ${this.CurrentHeatingCoolingState}`); // Set the TargetTemperature value based on the current mode - if (this.TargetHeatingCoolingState === this.platform.Characteristic.TargetHeatingCoolingState.HEAT) { + if (this.TargetHeatingCoolingState === this.hap.Characteristic.TargetHeatingCoolingState.HEAT) { if (this.device.changeableValues!.heatSetpoint > 0) { this.TargetTemperature = this.toCelsius(this.device.changeableValues!.heatSetpoint); } @@ -296,18 +310,26 @@ export class RoomSensorThermostat { */ async refreshStatus(): Promise { try { - const device: any = ( - await this.platform.axios.get(`${settings.DeviceURL}/thermostats/${this.device.deviceID}`, { - params: { - locationId: this.locationId, - }, - }) - ).data; + const { body, statusCode } = await request(`${DeviceURL}/thermostats/${this.device.deviceID}`, { + method: 'GET', + query: { + 'locationId': this.locationId, + 'apikey': this.config.credentials?.consumerKey, + }, + headers: { + 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, + 'Content-Type': 'application/json', + }, + }); + const action = 'refreshStatus'; + await this.statusCode(statusCode, action); + const device: any = await body.json(); + this.log.debug(`(refreshStatus) ${device.deviceClass}: ${JSON.stringify(device)}`); this.device = device; - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} device: ${superStringify(device)}`); - this.debugLog( + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} device: ${JSON.stringify(device)}`); + this.log.debug( `Room Sensor Thermostat: ${this.accessory.displayName}` + - ` Fetched update for: ${this.device.name} from Resideo API: ${superStringify(this.device.changeableValues)}`, + ` Fetched update for: ${this.device.name} from Resideo API: ${JSON.stringify(this.device.changeableValues)}`, ); this.parseStatus(); @@ -333,23 +355,23 @@ export class RoomSensorThermostat { const roomsensors = await this.platform.getCurrentSensorData(this.device, group, this.locationId); if (roomsensors.rooms) { const rooms = roomsensors.rooms; - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} roomsensors: ${superStringify(roomsensors)}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} roomsensors: ${JSON.stringify(roomsensors)}`); for (const accessories of rooms) { if (accessories) { - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} accessories: ${superStringify(accessories)}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} accessories: ${JSON.stringify(accessories)}`); for (const accessory of accessories.accessories) { if (accessory.accessoryAttribute) { if (accessory.accessoryAttribute.type) { if (accessory.accessoryAttribute.type.startsWith('IndoorAirSensor')) { this.sensorAccessory = accessory; - this.debugLog( + this.log.debug( `Room Sensor Thermostat: ${this.accessory.displayName}` + - ` accessoryAttribute: ${superStringify(this.sensorAccessory.accessoryAttribute)}`, + ` accessoryAttribute: ${JSON.stringify(this.sensorAccessory.accessoryAttribute)}`, ); - this.debugLog( + this.log.debug( `Room Sensor Thermostat: ${this.accessory.displayName}` + - ` Name: ${this.sensorAccessory.accessoryAttribute.name},` + - ` Software Version: ${this.sensorAccessory.accessoryAttribute.softwareRevision}`, + ` Name: ${this.sensorAccessory.accessoryAttribute.name},` + + ` Software Version: ${this.sensorAccessory.accessoryAttribute.softwareRevision}`, ); } } @@ -374,14 +396,22 @@ export class RoomSensorThermostat { async refreshRoomPriority(): Promise { if (this.device.thermostat?.roompriority?.deviceType === 'Thermostat') { - this.roompriority = ( - await this.platform.axios.get(`${settings.DeviceURL}/thermostats/${this.device.deviceID}/priority`, { - params: { - locationId: this.locationId, - }, - }) - ).data; - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} roompriority: ${superStringify(this.roompriority)}`); + const { body, statusCode } = await request(`${DeviceURL}/thermostats/${this.device.deviceID}/priority`, { + method: 'GET', + query: { + 'locationId': this.locationId, + 'apikey': this.config.credentials?.consumerKey, + }, + headers: { + 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, + 'Content-Type': 'application/json', + }, + }); + const action = 'refreshRoomPriority'; + await this.statusCode(statusCode, action); + const roompriority: any = await body.json(); + this.log.debug(`(refreshRoomPriority) roompriority: ${JSON.stringify(roompriority)}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} roompriority: ${JSON.stringify(this.roompriority)}`); } } @@ -389,8 +419,8 @@ export class RoomSensorThermostat { * Pushes the requested changes for Room Priority to the Resideo API */ async pushRoomChanges(): Promise { - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} Room Priority, - Current Room: ${superStringify(this.roompriority.currentPriority.selectedRooms)}, Changing Room: [${this.sensorAccessory.accessoryId}]`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} Room Priority, + Current Room: ${JSON.stringify(this.roompriority.currentPriority.selectedRooms)}, Changing Room: [${this.sensorAccessory.accessoryId}]`); if (`[${this.sensorAccessory.accessoryId}]` !== `[${this.roompriority.currentPriority.selectedRooms}]`) { const payload = { currentPriority: { @@ -410,29 +440,39 @@ export class RoomSensorThermostat { */ if (this.device.thermostat?.roompriority?.deviceType === 'Thermostat') { if (this.device.thermostat?.roompriority.priorityType === 'FollowMe') { - this.platform.log.info( + this.log.info( `Room Sensor Thermostat: ${this.accessory.displayName} sent request to Resideo API, Priority Type: ` + - `${this.device.thermostat?.roompriority.priorityType} Built-in Occupancy Sensor(s) Will be used to set Priority Automatically.`, + `${this.device.thermostat?.roompriority.priorityType} Built-in Occupancy Sensor(s) Will be used to set Priority Automatically.`, ); } else if (this.device.thermostat?.roompriority.priorityType === 'WholeHouse') { - this.platform.log.info( + this.log.info( `Room Sensor Thermostat: ${this.accessory.displayName} sent request to Resideo API,` + - ` Priority Type: ${this.device.thermostat?.roompriority.priorityType}`, + ` Priority Type: ${this.device.thermostat?.roompriority.priorityType}`, ); } else if (this.device.thermostat?.roompriority.priorityType === 'PickARoom') { - this.platform.log.info( + this.log.info( `Room Sensor Thermostat: ${this.accessory.displayName} sent request to Resideo API,` + - ` Room Priority: ${this.sensorAccessory.accessoryAttribute.name}, Priority Type: ${this.device.thermostat?.roompriority.priorityType}`, + ` Room Priority: ${this.sensorAccessory.accessoryAttribute.name}, Priority Type: ${this.device.thermostat?.roompriority.priorityType}`, ); } // Make the API request - await this.platform.axios.put(`${settings.DeviceURL}/thermostats/${this.device.deviceID}/priority`, payload, { - params: { - locationId: this.locationId, + const { body, statusCode } = await request(`${DeviceURL}/thermostats/${this.device.deviceID}/priority`, { + method: 'PUT', + body: JSON.stringify(payload), + query: { + 'locationId': this.locationId, + 'apikey': this.config.credentials?.consumerKey, + }, + headers: { + 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, + 'Content-Type': 'application/json', }, }); - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} pushRoomChanges: ${superStringify(payload)}`); + const action = 'pushRoomChanges'; + await this.statusCode(statusCode, action); + this.log.debug(`(pushRoomChanges) body: ${JSON.stringify(body)}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} pushRoomChanges: ${JSON.stringify(payload)}`); } // Refresh the status from the API await this.refreshSensorStatus(); @@ -448,59 +488,68 @@ export class RoomSensorThermostat { mode: this.resideoMode[Number(this.TargetHeatingCoolingState)], thermostatSetpointStatus: this.device.thermostat?.thermostatSetpointStatus, autoChangeoverActive: this.device.changeableValues!.autoChangeoverActive, - } as settings.payload; + } as payload; // Set the heat and cool set point value based on the selected mode switch (this.TargetHeatingCoolingState) { - case this.platform.Characteristic.TargetHeatingCoolingState.HEAT: + case this.hap.Characteristic.TargetHeatingCoolingState.HEAT: payload.heatSetpoint = this.toFahrenheit(Number(this.TargetTemperature)); payload.coolSetpoint = this.toFahrenheit(Number(this.CoolingThresholdTemperature)); - this.debugLog( + this.log.debug( `Room Sensor Thermostat: ${this.accessory.displayName}` + - ` TargetHeatingCoolingState (HEAT): ${this.TargetHeatingCoolingState},` + - ` TargetTemperature: ${this.toFahrenheit(Number(this.TargetTemperature))} heatSetpoint,` + - ` CoolingThresholdTemperature: ${this.toFahrenheit(Number(this.CoolingThresholdTemperature))} coolSetpoint`, + ` TargetHeatingCoolingState (HEAT): ${this.TargetHeatingCoolingState},` + + ` TargetTemperature: ${this.toFahrenheit(Number(this.TargetTemperature))} heatSetpoint,` + + ` CoolingThresholdTemperature: ${this.toFahrenheit(Number(this.CoolingThresholdTemperature))} coolSetpoint`, ); break; - case this.platform.Characteristic.TargetHeatingCoolingState.COOL: + case this.hap.Characteristic.TargetHeatingCoolingState.COOL: payload.coolSetpoint = this.toFahrenheit(Number(this.TargetTemperature)); payload.heatSetpoint = this.toFahrenheit(Number(this.HeatingThresholdTemperature)); - this.debugLog( + this.log.debug( `Room Sensor Thermostat: ${this.accessory.displayName}` + - ` TargetHeatingCoolingState (COOL): ${this.TargetHeatingCoolingState},` + - ` TargetTemperature: ${this.toFahrenheit(Number(this.TargetTemperature))} coolSetpoint,` + - ` CoolingThresholdTemperature: ${this.toFahrenheit(Number(this.HeatingThresholdTemperature))} heatSetpoint`, + ` TargetHeatingCoolingState (COOL): ${this.TargetHeatingCoolingState},` + + ` TargetTemperature: ${this.toFahrenheit(Number(this.TargetTemperature))} coolSetpoint,` + + ` CoolingThresholdTemperature: ${this.toFahrenheit(Number(this.HeatingThresholdTemperature))} heatSetpoint`, ); break; - case this.platform.Characteristic.TargetHeatingCoolingState.AUTO: + case this.hap.Characteristic.TargetHeatingCoolingState.AUTO: payload.coolSetpoint = this.toFahrenheit(Number(this.CoolingThresholdTemperature)); payload.heatSetpoint = this.toFahrenheit(Number(this.HeatingThresholdTemperature)); - this.debugLog( + this.log.debug( `Room Sensor Thermostat: ${this.accessory.displayName}` + - ` TargetHeatingCoolingState (AUTO): ${this.TargetHeatingCoolingState},` + - ` CoolingThresholdTemperature: ${this.toFahrenheit(Number(this.CoolingThresholdTemperature))} coolSetpoint,` + - ` HeatingThresholdTemperature: ${this.toFahrenheit(Number(this.HeatingThresholdTemperature))} heatSetpoint`, + ` TargetHeatingCoolingState (AUTO): ${this.TargetHeatingCoolingState},` + + ` CoolingThresholdTemperature: ${this.toFahrenheit(Number(this.CoolingThresholdTemperature))} coolSetpoint,` + + ` HeatingThresholdTemperature: ${this.toFahrenheit(Number(this.HeatingThresholdTemperature))} heatSetpoint`, ); break; default: payload.coolSetpoint = this.toFahrenheit(Number(this.CoolingThresholdTemperature)); payload.heatSetpoint = this.toFahrenheit(Number(this.HeatingThresholdTemperature)); - this.debugLog( + this.log.debug( `Room Sensor Thermostat: ${this.accessory.displayName}` + - ` TargetHeatingCoolingState (OFF): ${this.TargetHeatingCoolingState},` + - ` CoolingThresholdTemperature: ${this.toFahrenheit(Number(this.CoolingThresholdTemperature))} coolSetpoint,` + - ` HeatingThresholdTemperature: ${this.toFahrenheit(Number(this.HeatingThresholdTemperature))} heatSetpoint`, + ` TargetHeatingCoolingState (OFF): ${this.TargetHeatingCoolingState},` + + ` CoolingThresholdTemperature: ${this.toFahrenheit(Number(this.CoolingThresholdTemperature))} coolSetpoint,` + + ` HeatingThresholdTemperature: ${this.toFahrenheit(Number(this.HeatingThresholdTemperature))} heatSetpoint`, ); } - this.platform.log.info(`Room Sensor Thermostat: ${this.accessory.displayName} set request (${superStringify(payload)}) to Resideo API.`); + this.log.info(`Room Sensor Thermostat: ${this.accessory.displayName} set request (${JSON.stringify(payload)}) to Resideo API.`); // Make the API request - await this.platform.axios.post(`${settings.DeviceURL}/thermostats/${this.device.deviceID}`, payload, { - params: { - locationId: this.locationId, + const { statusCode } = await request(`${DeviceURL}/thermostats/${this.device.deviceID}`, { + method: 'POST', + body: JSON.stringify(payload), + query: { + 'locationId': this.locationId, + 'apikey': this.config.credentials?.consumerKey, + }, + headers: { + 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, + 'Content-Type': 'application/json', }, }); - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} pushChanges: ${superStringify(payload)}`); + const action = 'pushChanges'; + await this.statusCode(statusCode, action); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} pushChanges: ${JSON.stringify(payload)}`); } catch (e: any) { this.action = 'pushChanges'; this.resideoAPIError(e); @@ -513,81 +562,81 @@ export class RoomSensorThermostat { */ async updateHomeKitCharacteristics(): Promise { if (this.TemperatureDisplayUnits === undefined) { - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} TemperatureDisplayUnits: ${this.TemperatureDisplayUnits}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} TemperatureDisplayUnits: ${this.TemperatureDisplayUnits}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits, this.TemperatureDisplayUnits); - this.debugLog( + this.service.updateCharacteristic(this.hap.Characteristic.TemperatureDisplayUnits, this.TemperatureDisplayUnits); + this.log.debug( `Room Sensor Thermostat: ${this.accessory.displayName}` + ` updateCharacteristic TemperatureDisplayUnits: ${this.TemperatureDisplayUnits}`, ); } if (this.CurrentTemperature === undefined) { - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} CurrentTemperature: ${this.CurrentTemperature}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} CurrentTemperature: ${this.CurrentTemperature}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.CurrentTemperature); - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName}` + ` updateCharacteristic CurrentTemperature: ${this.CurrentTemperature}`); + this.service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, this.CurrentTemperature); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} updateCharacteristic CurrentTemperature: ${this.CurrentTemperature}`); } if (this.CurrentRelativeHumidity === undefined) { - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, this.CurrentRelativeHumidity); - this.debugLog( + this.service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, this.CurrentRelativeHumidity); + this.log.debug( `Room Sensor Thermostat: ${this.accessory.displayName}` + ` updateCharacteristic CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`, ); } if (this.TargetTemperature === undefined) { - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} TargetTemperature: ${this.TargetTemperature}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} TargetTemperature: ${this.TargetTemperature}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.TargetTemperature, this.TargetTemperature); - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} updateCharacteristic TargetTemperature: ${this.TargetTemperature}`); + this.service.updateCharacteristic(this.hap.Characteristic.TargetTemperature, this.TargetTemperature); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} updateCharacteristic TargetTemperature: ${this.TargetTemperature}`); } if (this.HeatingThresholdTemperature === undefined) { - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} HeatingThresholdTemperature: ${this.HeatingThresholdTemperature}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} HeatingThresholdTemperature: ${this.HeatingThresholdTemperature}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature, this.HeatingThresholdTemperature); - this.debugLog( + this.service.updateCharacteristic(this.hap.Characteristic.HeatingThresholdTemperature, this.HeatingThresholdTemperature); + this.log.debug( `Room Sensor Thermostat: ${this.accessory.displayName} updateCharacteristic` + - ` HeatingThresholdTemperature: ${this.HeatingThresholdTemperature}`, + ` HeatingThresholdTemperature: ${this.HeatingThresholdTemperature}`, ); } if (this.CoolingThresholdTemperature === undefined) { - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} CoolingThresholdTemperature: ${this.CoolingThresholdTemperature}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} CoolingThresholdTemperature: ${this.CoolingThresholdTemperature}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.CoolingThresholdTemperature, this.CoolingThresholdTemperature); - this.debugLog( + this.service.updateCharacteristic(this.hap.Characteristic.CoolingThresholdTemperature, this.CoolingThresholdTemperature); + this.log.debug( `Room Sensor Thermostat: ${this.accessory.displayName} updateCharacteristic` + - ` CoolingThresholdTemperature: ${this.CoolingThresholdTemperature}`, + ` CoolingThresholdTemperature: ${this.CoolingThresholdTemperature}`, ); } if (this.TargetHeatingCoolingState === undefined) { - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} TargetHeatingCoolingState: ${this.TargetHeatingCoolingState}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} TargetHeatingCoolingState: ${this.TargetHeatingCoolingState}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState, this.TargetHeatingCoolingState); - this.debugLog( + this.service.updateCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState, this.TargetHeatingCoolingState); + this.log.debug( `Room Sensor Thermostat: ${this.accessory.displayName} updateCharacteristic` + - ` TargetHeatingCoolingState: ${this.TargetHeatingCoolingState}`, + ` TargetHeatingCoolingState: ${this.TargetHeatingCoolingState}`, ); } if (this.CurrentHeatingCoolingState === undefined) { - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} CurrentHeatingCoolingState: ${this.CurrentHeatingCoolingState}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} CurrentHeatingCoolingState: ${this.CurrentHeatingCoolingState}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState, this.CurrentHeatingCoolingState); - this.debugLog( + this.service.updateCharacteristic(this.hap.Characteristic.CurrentHeatingCoolingState, this.CurrentHeatingCoolingState); + this.log.debug( `Room Sensor Thermostat: ${this.accessory.displayName} updateCharacteristic` + - ` CurrentHeatingCoolingState: ${this.TargetHeatingCoolingState}`, + ` CurrentHeatingCoolingState: ${this.TargetHeatingCoolingState}`, ); } } async apiError(e: any): Promise { - this.service.updateCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits, e); - this.service.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, e); - this.service.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, e); - this.service.updateCharacteristic(this.platform.Characteristic.TargetTemperature, e); - this.service.updateCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature, e); - this.service.updateCharacteristic(this.platform.Characteristic.CoolingThresholdTemperature, e); - this.service.updateCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState, e); - this.service.updateCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState, e); - //throw new this.platform.api.hap.HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE); + this.service.updateCharacteristic(this.hap.Characteristic.TemperatureDisplayUnits, e); + this.service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e); + this.service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, e); + this.service.updateCharacteristic(this.hap.Characteristic.TargetTemperature, e); + this.service.updateCharacteristic(this.hap.Characteristic.HeatingThresholdTemperature, e); + this.service.updateCharacteristic(this.hap.Characteristic.CoolingThresholdTemperature, e); + this.service.updateCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState, e); + this.service.updateCharacteristic(this.hap.Characteristic.CurrentHeatingCoolingState, e); + //throw new this.api.hap.HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE); } async resideoAPIError(e: any): Promise { @@ -627,57 +676,84 @@ export class RoomSensorThermostat { } } if (e.message.includes('400')) { - this.platform.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Bad Request`); - this.debugLog('The client has issued an invalid request. This is commonly used to specify validation errors in a request payload.'); + this.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Bad Request`); + this.log.debug('The client has issued an invalid request. This is commonly used to specify validation errors in a request payload.'); } else if (e.message.includes('401')) { - this.platform.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Unauthorized Request`); - this.debugLog('Authorization for the API is required, but the request has not been authenticated.'); + this.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Unauthorized Request`); + this.log.debug('Authorization for the API is required, but the request has not been authenticated.'); } else if (e.message.includes('403')) { - this.platform.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Forbidden Request`); - this.debugLog('The request has been authenticated but does not have appropriate permissions, or a requested resource is not found.'); + this.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Forbidden Request`); + this.log.debug('The request has been authenticated but does not have appropriate permissions, or a requested resource is not found.'); } else if (e.message.includes('404')) { - this.platform.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Requst Not Found`); - this.debugLog('Specifies the requested path does not exist.'); + this.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Requst Not Found`); + this.log.debug('Specifies the requested path does not exist.'); } else if (e.message.includes('406')) { - this.platform.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Request Not Acceptable`); - this.debugLog('The client has requested a MIME type via the Accept header for a value not supported by the server.'); + this.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Request Not Acceptable`); + this.log.debug('The client has requested a MIME type via the Accept header for a value not supported by the server.'); } else if (e.message.includes('415')) { - this.platform.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Unsupported Requst Header`); - this.debugLog('The client has defined a contentType header that is not supported by the server.'); + this.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Unsupported Requst Header`); + this.log.debug('The client has defined a contentType header that is not supported by the server.'); } else if (e.message.includes('422')) { - this.platform.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Unprocessable Entity`); - this.debugLog( + this.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Unprocessable Entity`); + this.log.debug( 'The client has made a valid request, but the server cannot process it.' + - ' This is often used for APIs for which certain limits have been exceeded.', + ' This is often used for APIs for which certain limits have been exceeded.', ); } else if (e.message.includes('429')) { - this.platform.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Too Many Requests`); - this.debugLog('The client has exceeded the number of requests allowed for a given time window.'); + this.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Too Many Requests`); + this.log.debug('The client has exceeded the number of requests allowed for a given time window.'); } else if (e.message.includes('500')) { - this.platform.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Internal Server Error`); - this.debugLog('An unexpected error on the SmartThings servers has occurred. These errors should be rare.'); + this.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action}, Internal Server Error`); + this.log.debug('An unexpected error on the SmartThings servers has occurred. These errors should be rare.'); } else { - this.platform.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action},`); + this.log.error(`Room Sensor Thermostat: ${this.accessory.displayName} failed to ${this.action},`); } if (this.deviceLogging.includes('debug')) { - this.platform.log.error( - `Room Sensor Thermostat: ${this.accessory.displayName} failed to pushChanges, ` + `Error Message: ${superStringify(e.message)}`, + this.log.error( + `Room Sensor Thermostat: ${this.accessory.displayName} failed to pushChanges, ` + `Error Message: ${JSON.stringify(e.message)}`, ); } } + async statusCode(statusCode: number, action: string): Promise { + switch (statusCode) { + case 200: + this.log.debug(`${this.device.deviceClass}: ${this.accessory.displayName} Standard Response, statusCode: ${statusCode}, Action: ${action}`); + break; + case 400: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Bad Request, statusCode: ${statusCode}, Action: ${action}`); + break; + case 401: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Unauthorized, statusCode: ${statusCode}, Action: ${action}`); + break; + case 404: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Not Found, statusCode: ${statusCode}, Action: ${action}`); + break; + case 429: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Too Many Requests, statusCode: ${statusCode}, Action: ${action}`); + break; + case 500: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Internal Server Error (Meater Server), statusCode: ${statusCode}, ` + + `Action: ${action}`); + break; + default: + this.log.info(`${this.device.deviceClass}: ${this.accessory.displayName} Unknown statusCode: ${statusCode}, ` + + `Action: ${action}, Report Bugs Here: https://bit.ly/homebridge-resideo-bug-report`); + } + } + async setTargetHeatingCoolingState(value: CharacteristicValue): Promise { - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} Set TargetHeatingCoolingState: ${value}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} Set TargetHeatingCoolingState: ${value}`); this.TargetHeatingCoolingState = value; // Set the TargetTemperature value based on the selected mode - if (this.TargetHeatingCoolingState === this.platform.Characteristic.TargetHeatingCoolingState.HEAT) { + if (this.TargetHeatingCoolingState === this.hap.Characteristic.TargetHeatingCoolingState.HEAT) { this.TargetTemperature = this.toCelsius(this.device.changeableValues!.heatSetpoint); } else { this.TargetTemperature = this.toCelsius(this.device.changeableValues!.coolSetpoint); } - this.service.updateCharacteristic(this.platform.Characteristic.TargetTemperature, this.TargetTemperature); + this.service.updateCharacteristic(this.hap.Characteristic.TargetTemperature, this.TargetTemperature); if (this.TargetHeatingCoolingState !== this.modes[this.device.changeableValues!.mode]) { this.doRoomUpdate.next(); this.doThermostatUpdate.next(); @@ -685,30 +761,30 @@ export class RoomSensorThermostat { } async setHeatingThresholdTemperature(value: CharacteristicValue): Promise { - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} Set HeatingThresholdTemperature: ${value}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} Set HeatingThresholdTemperature: ${value}`); this.HeatingThresholdTemperature = value; this.doThermostatUpdate.next(); } async setCoolingThresholdTemperature(value: CharacteristicValue): Promise { - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} Set CoolingThresholdTemperature: ${value}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} Set CoolingThresholdTemperature: ${value}`); this.CoolingThresholdTemperature = value; this.doThermostatUpdate.next(); } async setTargetTemperature(value: CharacteristicValue): Promise { - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} Set TargetTemperature: ${value}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} Set TargetTemperature: ${value}`); this.TargetTemperature = value; this.doThermostatUpdate.next(); } async setTemperatureDisplayUnits(value: CharacteristicValue): Promise { - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} Set TemperatureDisplayUnits: ${value}`); - this.platform.log.warn('Changing the Hardware Display Units from HomeKit is not supported.'); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} Set TemperatureDisplayUnits: ${value}`); + this.log.warn('Changing the Hardware Display Units from HomeKit is not supported.'); // change the temp units back to the one the Resideo API said the thermostat was set to setTimeout(() => { - this.service.updateCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits, this.TemperatureDisplayUnits); + this.service.updateCharacteristic(this.hap.Characteristic.TemperatureDisplayUnits, this.TemperatureDisplayUnits); }, 100); } @@ -716,7 +792,7 @@ export class RoomSensorThermostat { * Converts the value to celsius if the temperature units are in Fahrenheit */ toCelsius(value: number): number { - if (this.TemperatureDisplayUnits === this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS) { + if (this.TemperatureDisplayUnits === this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS) { return value; } @@ -728,7 +804,7 @@ export class RoomSensorThermostat { * Converts the value to fahrenheit if the temperature units are in Fahrenheit */ toFahrenheit(value: number): number { - if (this.TemperatureDisplayUnits === this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS) { + if (this.TemperatureDisplayUnits === this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS) { return value; } @@ -736,71 +812,26 @@ export class RoomSensorThermostat { } TargetState(): number[] { - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} allowedModes: ${this.device.allowedModes}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} allowedModes: ${this.device.allowedModes}`); const TargetState = [4]; TargetState.pop(); if (this.device.allowedModes!.includes('Cool')) { - TargetState.push(this.platform.Characteristic.TargetHeatingCoolingState.COOL); + TargetState.push(this.hap.Characteristic.TargetHeatingCoolingState.COOL); } if (this.device.allowedModes!.includes('Heat')) { - TargetState.push(this.platform.Characteristic.TargetHeatingCoolingState.HEAT); + TargetState.push(this.hap.Characteristic.TargetHeatingCoolingState.HEAT); } if (this.device.allowedModes!.includes('Off')) { - TargetState.push(this.platform.Characteristic.TargetHeatingCoolingState.OFF); + TargetState.push(this.hap.Characteristic.TargetHeatingCoolingState.OFF); } if (this.device.allowedModes!.includes('Auto')) { - TargetState.push(this.platform.Characteristic.TargetHeatingCoolingState.AUTO); + TargetState.push(this.hap.Characteristic.TargetHeatingCoolingState.AUTO); } - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} Only Show These Modes: ${superStringify(TargetState)}`); + this.log.debug(`Room Sensor Thermostat: ${this.accessory.displayName} Only Show These Modes: ${JSON.stringify(TargetState)}`); return TargetState; } - async config(device: settings.device & settings.devicesConfig): Promise { - let config = {}; - if (device.thermostat?.roompriority) { - config = device.thermostat?.roompriority; - } - if (device.thermostat?.roompriority?.logging !== undefined) { - config['logging'] = device.thermostat?.roompriority?.logging; - } - if (device.thermostat?.roompriority?.refreshRate !== undefined) { - config['refreshRate'] = device.thermostat?.roompriority?.refreshRate; - } - if (Object.entries(config).length !== 0) { - this.infoLog(`Room Sensor Thermostat: ${this.accessory.displayName} Config: ${superStringify(config)}`); - } - } - - async refreshRate(device: settings.device & settings.devicesConfig): Promise { - if (device.thermostat?.roompriority?.refreshRate) { - this.deviceRefreshRate = this.accessory.context.refreshRate = device.thermostat?.roompriority?.refreshRate; - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} Using Device Config refreshRate: ${this.deviceRefreshRate}`); - } else if (device.refreshRate) { - this.deviceRefreshRate = this.accessory.context.refreshRate = device.refreshRate; - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} Using Thermostat Device Config refreshRate: ${this.deviceRefreshRate}`); - } else if (this.platform.config.options!.refreshRate) { - this.deviceRefreshRate = this.accessory.context.refreshRate = this.platform.config.options!.refreshRate; - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} Using Platform Config refreshRate: ${this.deviceRefreshRate}`); - } - } - - async logs(device: settings.device & settings.devicesConfig): Promise { - if (this.platform.debugMode) { - this.deviceLogging = this.accessory.context.logging = 'debugMode'; - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} Using Debug Mode Logging: ${this.deviceLogging}`); - } else if (device.thermostat?.roompriority?.logging) { - this.deviceLogging = this.accessory.context.logging = device.thermostat?.roompriority?.logging; - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} Using Device Config Logging: ${this.deviceLogging}`); - } else if (this.platform.config.options?.logging) { - this.deviceLogging = this.accessory.context.logging = this.platform.config.options?.logging; - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} Using Platform Config Logging: ${this.deviceLogging}`); - } else { - this.deviceLogging = this.accessory.context.logging = 'standard'; - this.debugLog(`Room Sensor Thermostat: ${this.accessory.displayName} Logging Not Set, Using: ${this.deviceLogging}`); - } - } - /** * Logging for Device */ @@ -816,7 +847,7 @@ export class RoomSensorThermostat { } } - debugWarnLog(...log: any[]): void { + debugWarnLog({ log = [] }: { log?: any[]; } = {}): void { if (this.enablingDeviceLogging()) { if (this.deviceLogging?.includes('debug')) { this.platform.log.warn('[DEBUG]', String(...log)); diff --git a/src/devices/thermostats.ts b/src/devices/thermostats.ts index 201ac149..1efe4e70 100644 --- a/src/devices/thermostats.ts +++ b/src/devices/thermostats.ts @@ -1,9 +1,11 @@ -import { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import { request } from 'undici'; import { interval, Subject } from 'rxjs'; -import superStringify from 'super-stringify'; -import { debounceTime, skipWhile, take, tap } from 'rxjs/operators'; -import { ResideoPlatform } from '../platform'; -import * as settings from '../settings'; +import { ResideoPlatform } from '../platform.js'; +import { debounceTime, take, tap, skipWhile } from 'rxjs/operators'; +import { API, CharacteristicValue, HAP, Service, PlatformAccessory, Logging } from 'homebridge'; +import { + DeviceURL, FanChangeableValues, devicesConfig, holdModes, location, modes, resideoDevice, payload, ResideoPlatformConfig, +} from '../settings.js'; /** * Platform Accessory @@ -11,6 +13,11 @@ import * as settings from '../settings'; * Each accessory may expose multiple services of different service types. */ export class Thermostats { + public readonly api: API; + public readonly log: Logging; + public readonly config!: ResideoPlatformConfig; + protected readonly hap: HAP; + // Services service!: Service; fanService?: Service; humidityService?: Service; @@ -33,15 +40,15 @@ export class Thermostats { TargetFanState!: CharacteristicValue; // Others - modes: settings.modes; - holdModes: settings.holdModes; + modes: modes; + holdModes: holdModes; action!: string; heatSetpoint!: number; coolSetpoint!: number; thermostatSetpointStatus!: string; resideoMode!: Array; resideoHold!: Array; - fanMode!: settings.FanChangeableValues; + fanMode!: FanChangeableValues; // Others - T9 Only roompriority!: any; @@ -64,25 +71,44 @@ export class Thermostats { constructor( private readonly platform: ResideoPlatform, - private accessory: PlatformAccessory, - public readonly locationId: settings.location['locationID'], - public device: settings.device & settings.devicesConfig, + private readonly accessory: PlatformAccessory, + public readonly locationId: location['locationID'], + public device: resideoDevice & devicesConfig, ) { - this.logs(device); - this.refreshRate(device); - this.config(device); + this.api = this.platform.api; + this.log = this.platform.log; + this.config = this.platform.config; + this.hap = this.api.hap; + + + this.TargetTemperature = accessory.context.TargetTemperature || 20; + this.CurrentTemperature = accessory.context.CurrentTemperature || 20; + this.CurrentRelativeHumidity = accessory.context.CurrentRelativeHumidity || 50; + this.TemperatureDisplayUnits = accessory.context.TemperatureDisplayUnits || this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS; + this.ProgrammableSwitchEvent = accessory.context.ProgrammableSwitchEvent || this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS; + this.TargetHeatingCoolingState = accessory.context.TargetHeatingCoolingState || this.hap.Characteristic.TargetHeatingCoolingState.AUTO; + this.CurrentHeatingCoolingState = accessory.context.CurrentHeatingCoolingState || this.hap.Characteristic.CurrentHeatingCoolingState.OFF; + this.CoolingThresholdTemperature = accessory.context.CoolingThresholdTemperature || 20; + this.HeatingThresholdTemperature = accessory.context.HeatingThresholdTemperature || 22; + this.ProgrammableSwitchOutputState = accessory.context.ProgrammableSwitchOutputState || 0; + accessory.context.FirmwareRevision = 'v2.0.0'; + + this.deviceLogging = this.device.logging || this.config.options?.logging || 'standard'; + + this.Active = accessory.context.Active || this.hap.Characteristic.Active.ACTIVE; + this.TargetFanState = accessory.context.TargetFanState || this.hap.Characteristic.TargetFanState.MANUAL; // Map Resideo Modes to HomeKit Modes this.modes = { - Off: platform.Characteristic.TargetHeatingCoolingState.OFF, - Heat: platform.Characteristic.TargetHeatingCoolingState.HEAT, - Cool: platform.Characteristic.TargetHeatingCoolingState.COOL, - Auto: platform.Characteristic.TargetHeatingCoolingState.AUTO, + Off: this.hap.Characteristic.TargetHeatingCoolingState.OFF, + Heat: this.hap.Characteristic.TargetHeatingCoolingState.HEAT, + Cool: this.hap.Characteristic.TargetHeatingCoolingState.COOL, + Auto: this.hap.Characteristic.TargetHeatingCoolingState.AUTO, }; // Map Resideo Hold Modes to HomeKit StatefulProgrammableSwitch Events this.holdModes = { - NoHold: platform.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS, - TemporaryHold: platform.Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS, - PermanentHold: platform.Characteristic.ProgrammableSwitchEvent.LONG_PRESS, + NoHold: this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS, + TemporaryHold: this.hap.Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS, + PermanentHold: this.hap.Characteristic.ProgrammableSwitchEvent.LONG_PRESS, }; // Map HomeKit Modes to Resideo Modes @@ -90,13 +116,10 @@ export class Thermostats { this.resideoMode = ['Off', 'Heat', 'Cool', 'Auto']; this.resideoHold = ['NoHold', 'TemporaryHold', 'PermanentHold']; - // default placeholders - this.Active = this.platform.Characteristic.Active.INACTIVE; - this.TargetFanState = this.platform.Characteristic.TargetFanState.MANUAL; if (this.thermostatSetpointStatus === undefined) { accessory.context.thermostatSetpointStatus = device.thermostat?.thermostatSetpointStatus; this.thermostatSetpointStatus = accessory.context.thermostatSetpointStatus; - this.debugLog(`Thermostat: ${accessory.displayName} thermostatSetpointStatus: ${this.thermostatSetpointStatus}`); + this.log.debug(`Thermostat: ${accessory.displayName} thermostatSetpointStatus: ${this.thermostatSetpointStatus}`); } // this is subject we use to track when we need to POST changes to the Resideo API for Room Changes - T9 Only @@ -105,25 +128,24 @@ export class Thermostats { // this is subject we use to track when we need to POST changes to the Resideo API this.doThermostatUpdate = new Subject(); this.thermostatUpdateInProgress = false; + // this is subject we use to track when we need to POST changes to the Resideo API this.doFanUpdate = new Subject(); this.fanUpdateInProgress = false; // set accessory information accessory - .getService(this.platform.Service.AccessoryInformation)! - .setCharacteristic(this.platform.Characteristic.Manufacturer, 'Resideo') - .setCharacteristic(this.platform.Characteristic.Model, device.deviceModel) - .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceID) - .setCharacteristic(this.platform.Characteristic.FirmwareRevision, accessory.context.firmwareRevision) - .getCharacteristic(this.platform.Characteristic.FirmwareRevision) - .updateValue(accessory.context.firmwareRevision); + .getService(this.hap.Service.AccessoryInformation)! + .setCharacteristic(this.hap.Characteristic.Manufacturer, 'Resideo') + .setCharacteristic(this.hap.Characteristic.Model, device.deviceModel) + .setCharacteristic(this.hap.Characteristic.SerialNumber, device.deviceID) + .setCharacteristic(this.hap.Characteristic.FirmwareRevision, accessory.context.firmwareRevision || 'v2.0.0'); //Thermostat Service - (this.service = this.accessory.getService(this.platform.Service.Thermostat) || this.accessory.addService(this.platform.Service.Thermostat)), - accessory.displayName; + (this.service = this.accessory.getService(this.hap.Service.Thermostat) + || this.accessory.addService(this.hap.Service.Thermostat)), accessory.displayName; //Service Name - this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.displayName); + this.service.setCharacteristic(this.hap.Characteristic.Name, accessory.displayName); //Required Characteristics" see https://developers.homebridge.io/#/service/Thermostat //Initial Device Parse @@ -131,9 +153,9 @@ export class Thermostats { // Set Min and Max if (device.changeableValues!.heatCoolMode === 'Heat') { - this.debugLog(`Thermostat: ${accessory.displayName} is in "${device.changeableValues!.heatCoolMode}" mode`); + this.log.debug(`Thermostat: ${accessory.displayName} is in "${device.changeableValues!.heatCoolMode}" mode`); this.service - .getCharacteristic(this.platform.Characteristic.TargetTemperature) + .getCharacteristic(this.hap.Characteristic.TargetTemperature) .setProps({ minValue: this.toCelsius(device.minHeatSetpoint!), maxValue: this.toCelsius(device.maxHeatSetpoint!), @@ -143,9 +165,9 @@ export class Thermostats { return this.TargetTemperature!; }); } else { - this.debugLog(`Thermostat: ${accessory.displayName} is in "${device.changeableValues!.heatCoolMode}" mode`); + this.log.debug(`Thermostat: ${accessory.displayName} is in "${device.changeableValues!.heatCoolMode}" mode`); this.service - .getCharacteristic(this.platform.Characteristic.TargetTemperature) + .getCharacteristic(this.hap.Characteristic.TargetTemperature) .setProps({ minValue: this.toCelsius(device.minCoolSetpoint!), maxValue: this.toCelsius(device.maxCoolSetpoint!), @@ -161,57 +183,57 @@ export class Thermostats { // Set control bindings const TargetState = this.TargetState(); this.service - .getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState) + .getCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState) .setProps({ validValues: TargetState, }) .onSet(this.setTargetHeatingCoolingState.bind(this)); - this.service.setCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState, this.CurrentHeatingCoolingState); + this.service.setCharacteristic(this.hap.Characteristic.CurrentHeatingCoolingState, this.CurrentHeatingCoolingState); - this.service.getCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature).onSet(this.setHeatingThresholdTemperature.bind(this)); + this.service.getCharacteristic(this.hap.Characteristic.HeatingThresholdTemperature).onSet(this.setHeatingThresholdTemperature.bind(this)); - this.service.getCharacteristic(this.platform.Characteristic.CoolingThresholdTemperature).onSet(this.setCoolingThresholdTemperature.bind(this)); + this.service.getCharacteristic(this.hap.Characteristic.CoolingThresholdTemperature).onSet(this.setCoolingThresholdTemperature.bind(this)); - this.service.getCharacteristic(this.platform.Characteristic.TargetTemperature).onSet(this.setTargetTemperature.bind(this)); + this.service.getCharacteristic(this.hap.Characteristic.TargetTemperature).onSet(this.setTargetTemperature.bind(this)); - this.service.getCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits).onSet(this.setTemperatureDisplayUnits.bind(this)); + this.service.getCharacteristic(this.hap.Characteristic.TemperatureDisplayUnits).onSet(this.setTemperatureDisplayUnits.bind(this)); // Fan Controls if (device.thermostat?.hide_fan) { - this.debugLog(`Thermostat: ${accessory.displayName} Removing Fanv2 Service`); - this.fanService = this.accessory.getService(this.platform.Service.Fanv2); + this.log.debug(`Thermostat: ${accessory.displayName} Removing Fanv2 Service`); + this.fanService = this.accessory.getService(this.hap.Service.Fanv2); accessory.removeService(this.fanService!); } else if (!this.fanService && device.settings?.fan) { - this.debugLog(`Thermostat: ${accessory.displayName} Add Fanv2 Service`); - this.debugLog(`Thermostat: ${accessory.displayName} Available Fan Settings ${superStringify(device.settings.fan)}`); - (this.fanService = this.accessory.getService(this.platform.Service.Fanv2) || this.accessory.addService(this.platform.Service.Fanv2)), - `${accessory.displayName} Fan`; + this.log.debug(`Thermostat: ${accessory.displayName} Add Fanv2 Service`); + this.log.debug(`Thermostat: ${accessory.displayName} Available Fan Settings ${JSON.stringify(device.settings.fan)}`); + (this.fanService = this.accessory.getService(this.hap.Service.Fanv2) + || this.accessory.addService(this.hap.Service.Fanv2)), `${accessory.displayName} Fan`; - this.fanService.setCharacteristic(this.platform.Characteristic.Name, `${accessory.displayName} Fan`); + this.fanService.setCharacteristic(this.hap.Characteristic.Name, `${accessory.displayName} Fan`); - this.fanService.getCharacteristic(this.platform.Characteristic.Active).onSet(this.setActive.bind(this)); + this.fanService.getCharacteristic(this.hap.Characteristic.Active).onSet(this.setActive.bind(this)); - this.fanService.getCharacteristic(this.platform.Characteristic.TargetFanState).onSet(this.setTargetFanState.bind(this)); + this.fanService.getCharacteristic(this.hap.Characteristic.TargetFanState).onSet(this.setTargetFanState.bind(this)); } else { - this.debugLog(`Thermostat: ${accessory.displayName} Fanv2 Service Not Added`); + this.log.debug(`Thermostat: ${accessory.displayName} Fanv2 Service Not Added`); } // Humidity Sensor Service if (device.thermostat?.hide_humidity) { - this.debugLog(`Thermostat: ${accessory.displayName} Removing Humidity Sensor Service`); - this.humidityService = this.accessory.getService(this.platform.Service.HumiditySensor); + this.log.debug(`Thermostat: ${accessory.displayName} Removing Humidity Sensor Service`); + this.humidityService = this.accessory.getService(this.hap.Service.HumiditySensor); accessory.removeService(this.humidityService!); } else if (!this.humidityService && device.indoorHumidity) { - this.debugLog(`Thermostat: ${accessory.displayName} Add Humidity Sensor Service`); + this.log.debug(`Thermostat: ${accessory.displayName} Add Humidity Sensor Service`); (this.humidityService = - this.accessory.getService(this.platform.Service.HumiditySensor) || this.accessory.addService(this.platform.Service.HumiditySensor)), - `${device.name} Humidity Sensor`; + this.accessory.getService(this.hap.Service.HumiditySensor) + || this.accessory.addService(this.hap.Service.HumiditySensor)), `${device.name} Humidity Sensor`; - this.humidityService.setCharacteristic(this.platform.Characteristic.Name, `${accessory.displayName} Humidity Sensor`); + this.humidityService.setCharacteristic(this.hap.Characteristic.Name, `${accessory.displayName} Humidity Sensor`); this.humidityService - .getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity) + .getCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity) .setProps({ minStep: 0.1, }) @@ -219,27 +241,26 @@ export class Thermostats { return this.CurrentRelativeHumidity!; }); } else { - this.debugLog(`Thermostat: ${accessory.displayName} Humidity Sensor Service Not Added`); + this.log.debug(`Thermostat: ${accessory.displayName} Humidity Sensor Service Not Added`); } // get the StatefulProgrammableSwitch service if it exists, otherwise create a new StatefulProgrammableSwitch service // you can create multiple services for each accessory (this.statefulService = - accessory.getService(this.platform.Service.StatefulProgrammableSwitch) || - accessory.addService(this.platform.Service.StatefulProgrammableSwitch)), - `${accessory.displayName} ${device.deviceModel}`; + accessory.getService(this.hap.Service.StatefulProgrammableSwitch) + || accessory.addService(this.hap.Service.StatefulProgrammableSwitch)), `${accessory.displayName} ${device.deviceModel}`; - this.statefulService.setCharacteristic(this.platform.Characteristic.Name, accessory.displayName); - if (!this.statefulService.testCharacteristic(this.platform.Characteristic.ConfiguredName)) { - this.statefulService.addCharacteristic(this.platform.Characteristic.ConfiguredName, accessory.displayName); + this.statefulService.setCharacteristic(this.hap.Characteristic.Name, accessory.displayName); + if (!this.statefulService.testCharacteristic(this.hap.Characteristic.ConfiguredName)) { + this.statefulService.addCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.displayName); } // create handlers for required characteristics - this.statefulService.getCharacteristic(this.platform.Characteristic.ProgrammableSwitchEvent) + this.statefulService.getCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent) .onGet(this.handleProgrammableSwitchEventGet.bind(this)); this.statefulService - .getCharacteristic(this.platform.Characteristic.ProgrammableSwitchOutputState) + .getCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState) .onGet(this.handleProgrammableSwitchOutputStateGet.bind(this)) .onSet(this.handleProgrammableSwitchOutputStateSet.bind(this)); @@ -248,7 +269,7 @@ export class Thermostats { this.updateHomeKitCharacteristics(); // Start an update interval - interval(this.platform.config.options!.refreshRate! * 1000) + interval(this.config.options!.refreshRate! * 1000) .pipe(skipWhile(() => this.thermostatUpdateInProgress)) .subscribe(async () => { await this.refreshStatus(); @@ -262,7 +283,7 @@ export class Thermostats { tap(() => { this.roomUpdateInProgress = true; }), - debounceTime(this.platform.config.options!.pushRate! * 500), + debounceTime(this.config.options!.pushRate! * 500), ) .subscribe(async () => { try { @@ -296,7 +317,7 @@ export class Thermostats { tap(() => { this.thermostatUpdateInProgress = true; }), - debounceTime(this.platform.config.options!.pushRate! * 1000), + debounceTime(this.config.options!.pushRate! * 1000), ) .subscribe(async () => { try { @@ -322,7 +343,7 @@ export class Thermostats { tap(() => { this.fanUpdateInProgress = true; }), - debounceTime(this.platform.config.options!.pushRate! * 1000), + debounceTime(this.config.options!.pushRate! * 1000), ) .subscribe(async () => { try { @@ -349,48 +370,48 @@ export class Thermostats { * Parse the device status from the Resideo api */ async parseStatus(): Promise { - this.debugLog(`Thermostat: ${this.accessory.displayName} parseStatus`); + this.log.debug(`Thermostat: ${this.accessory.displayName} parseStatus`); if (this.device.units === 'Fahrenheit') { - this.TemperatureDisplayUnits = this.platform.Characteristic.TemperatureDisplayUnits.FAHRENHEIT; - this.debugLog( + this.TemperatureDisplayUnits = this.hap.Characteristic.TemperatureDisplayUnits.FAHRENHEIT; + this.log.debug( `Thermostat: ${this.accessory.displayName} parseStatus` + - ` TemperatureDisplayUnits: ${this.platform.Characteristic.TemperatureDisplayUnits.FAHRENHEIT}`, + ` TemperatureDisplayUnits: ${this.hap.Characteristic.TemperatureDisplayUnits.FAHRENHEIT}`, ); } if (this.device.units === 'Celsius') { - this.TemperatureDisplayUnits = this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS; - this.debugLog( + this.TemperatureDisplayUnits = this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS; + this.log.debug( `Thermostat: ${this.accessory.displayName} parseStatus` + - ` TemperatureDisplayUnits: ${this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS}`, + ` TemperatureDisplayUnits: ${this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS}`, ); } this.CurrentTemperature = this.toCelsius(this.device.indoorTemperature!); - this.debugLog(`Thermostat: ${this.accessory.displayName} parseStatus` + ` CurrentTemperature: ${this.toCelsius(this.device.indoorTemperature!)}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} parseStatus CurrentTemperature: ${this.toCelsius(this.device.indoorTemperature!)}`); if (this.device.indoorHumidity) { this.CurrentRelativeHumidity = this.device.indoorHumidity; - this.debugLog(`Thermostat: ${this.accessory.displayName} parseStatus` + ` CurrentRelativeHumidity: ${this.device.indoorHumidity}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} parseStatus` + ` CurrentRelativeHumidity: ${this.device.indoorHumidity}`); } if (this.device.changeableValues!.heatSetpoint > 0) { this.HeatingThresholdTemperature = this.toCelsius(this.device.changeableValues!.heatSetpoint); - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName} parseStatus` + - ` HeatingThresholdTemperature: ${this.toCelsius(this.device.changeableValues!.heatSetpoint)}`, + ` HeatingThresholdTemperature: ${this.toCelsius(this.device.changeableValues!.heatSetpoint)}`, ); } if (this.device.changeableValues!.coolSetpoint > 0) { this.CoolingThresholdTemperature = this.toCelsius(this.device.changeableValues!.coolSetpoint); - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName} parseStatus` + - ` CoolingThresholdTemperature: ${this.toCelsius(this.device.changeableValues!.coolSetpoint)}`, + ` CoolingThresholdTemperature: ${this.toCelsius(this.device.changeableValues!.coolSetpoint)}`, ); } this.TargetHeatingCoolingState = this.modes[this.device.changeableValues!.mode]; - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName} parseStatus` + ` TargetHeatingCoolingState: ${this.modes[this.device.changeableValues!.mode]}`, ); @@ -401,41 +422,41 @@ export class Thermostats { switch (this.device.operationStatus!.mode) { case 'Heat': this.CurrentHeatingCoolingState = 1; - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName}` + - ` parseStatus Currently Mode (HEAT): ${this.device.operationStatus!.mode}(${this.CurrentHeatingCoolingState})`, + ` parseStatus Currently Mode (HEAT): ${this.device.operationStatus!.mode}(${this.CurrentHeatingCoolingState})`, ); break; case 'Cool': this.CurrentHeatingCoolingState = 2; - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName}` + - ` parseStatus Currently Mode (COOL): ${this.device.operationStatus!.mode}(${this.CurrentHeatingCoolingState})`, + ` parseStatus Currently Mode (COOL): ${this.device.operationStatus!.mode}(${this.CurrentHeatingCoolingState})`, ); break; default: this.CurrentHeatingCoolingState = 0; - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName}` + - ` parseStatus Currently Mode (OFF): ${this.device.operationStatus!.mode}(${this.CurrentHeatingCoolingState})`, + ` parseStatus Currently Mode (OFF): ${this.device.operationStatus!.mode}(${this.CurrentHeatingCoolingState})`, ); } // Set the TargetTemperature value based on the current mode - if (this.TargetHeatingCoolingState === this.platform.Characteristic.TargetHeatingCoolingState.HEAT) { + if (this.TargetHeatingCoolingState === this.hap.Characteristic.TargetHeatingCoolingState.HEAT) { if (this.device.changeableValues!.heatSetpoint > 0) { this.TargetTemperature = this.toCelsius(this.device.changeableValues!.heatSetpoint); - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName}` + - ` parseStatus TargetTemperature (HEAT): ${this.toCelsius(this.device.changeableValues!.heatSetpoint)})`, + ` parseStatus TargetTemperature (HEAT): ${this.toCelsius(this.device.changeableValues!.heatSetpoint)})`, ); } } else { if (this.device.changeableValues!.coolSetpoint > 0) { this.TargetTemperature = this.toCelsius(this.device.changeableValues!.coolSetpoint); - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName}` + - ` parseStatus TargetTemperature (OFF/COOL): ${this.toCelsius(this.device.changeableValues!.coolSetpoint)})`, + ` parseStatus TargetTemperature (OFF/COOL): ${this.toCelsius(this.device.changeableValues!.coolSetpoint)})`, ); } } @@ -443,16 +464,16 @@ export class Thermostats { // Set the Target Fan State if (this.device.settings?.fan && !this.device.thermostat?.hide_fan) { if (this.fanMode) { - this.debugLog(`Thermostat: ${this.accessory.displayName} Fan: ${superStringify(this.fanMode)}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} Fan: ${JSON.stringify(this.fanMode)}`); if (this.fanMode.mode === 'Auto') { - this.TargetFanState = this.platform.Characteristic.TargetFanState.AUTO; - this.Active = this.platform.Characteristic.Active.INACTIVE; + this.TargetFanState = this.hap.Characteristic.TargetFanState.AUTO; + this.Active = this.hap.Characteristic.Active.INACTIVE; } else if (this.fanMode.mode === 'On') { - this.TargetFanState = this.platform.Characteristic.TargetFanState.MANUAL; - this.Active = this.platform.Characteristic.Active.ACTIVE; + this.TargetFanState = this.hap.Characteristic.TargetFanState.MANUAL; + this.Active = this.hap.Characteristic.Active.ACTIVE; } else if (this.fanMode.mode === 'Circulate') { - this.TargetFanState = this.platform.Characteristic.TargetFanState.MANUAL; - this.Active = this.platform.Characteristic.Active.INACTIVE; + this.TargetFanState = this.hap.Characteristic.TargetFanState.MANUAL; + this.Active = this.hap.Characteristic.Active.INACTIVE; } } } @@ -463,30 +484,47 @@ export class Thermostats { */ async refreshStatus(): Promise { try { - const device: any = ( - await this.platform.axios.get(`${settings.DeviceURL}/thermostats/${this.device.deviceID}`, { - params: { - locationId: this.locationId, - }, - }) - ).data; + const { body, statusCode } = await request(`${DeviceURL}/thermostats/${this.device.deviceID}`, { + method: 'GET', + query: { + 'locationId': this.locationId, + 'apikey': this.config.credentials?.consumerKey, + }, + headers: { + 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, + 'Content-Type': 'application/json', + }, + }); + const action = 'refreshStatus'; + await this.statusCode(statusCode, action); + const device: any = await body.json(); + this.log.debug(`(refreshStatus) ${device.deviceClass}: ${JSON.stringify(device)}`); this.device = device; - this.debugLog(`Thermostat: ${this.accessory.displayName} device: ${superStringify(this.device)}`); - this.debugLog(`Thermostat: ${this.accessory.displayName} refreshStatus for ${this.device.name} - from Resideo API: ${superStringify(this.device.changeableValues)}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} device: ${JSON.stringify(this.device)}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} refreshStatus for ${this.device.name}` + + `from Resideo API: ${JSON.stringify(this.device.changeableValues)}`); await this.refreshRoomPriority(); if (this.device.settings?.fan && !device.thermostat?.hide_fan) { - const fanMode: any = ( - await this.platform.axios.get(`${settings.DeviceURL}/thermostats/${this.device.deviceID}/fan`, { - params: { - locationId: this.locationId, - }, - }) - ).data; + const { body, statusCode } = await request(`${DeviceURL}/thermostats/${this.device.deviceID}/fan`, { + method: 'GET', + query: { + 'locationId': this.locationId, + 'apikey': this.config.credentials?.consumerKey, + }, + headers: { + 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, + 'Content-Type': 'application/json', + }, + }); + const action = 'refreshStatus/fan'; + await this.statusCode(statusCode, action); + this.log.debug(`(refreshStatus:fan) statusCode: ${statusCode}`); + const fanMode: any = await body.json(); + this.log.debug(`(refreshStatus:fan) Fan Mode: ${JSON.stringify(fanMode)}`); this.fanMode = fanMode; - this.debugLog(`Thermostat: ${this.accessory.displayName} fanMode: ${superStringify(this.fanMode)}`); - this.debugLog(`Thermostat: ${this.accessory.displayName} refreshStatus for ${this.device.name} Fan - from Resideo Fan API: ${superStringify(this.fanMode)}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} fanMode: ${JSON.stringify(this.fanMode)}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} refreshStatus for ${this.device.name} Fan` + + `from Resideo Fan API: ${JSON.stringify(this.fanMode)}`); } this.pushChangesthermostatSetpointStatus(); this.parseStatus(); @@ -500,14 +538,22 @@ export class Thermostats { async refreshRoomPriority(): Promise { if (this.device.thermostat?.roompriority?.deviceType === 'Thermostat' && this.device.deviceModel === 'T9-T10') { - this.roompriority = ( - await this.platform.axios.get(`${settings.DeviceURL}/thermostats/${this.device.deviceID}/priority`, { - params: { - locationId: this.locationId, - }, - }) - ).data; - this.debugLog(`Thermostat: ${this.accessory.displayName} Priority: ${superStringify(this.roompriority.data)}`); + const { body, statusCode } = await request(`${DeviceURL}/thermostats/${this.device.deviceID}/priority`, { + method: 'GET', + query: { + 'locationId': this.locationId, + 'apikey': this.config.credentials?.consumerKey, + }, + headers: { + 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, + 'Content-Type': 'application/json', + }, + }); + const action = 'refreshRoomPriority'; + await this.statusCode(statusCode, action); + const roompriority: any = await body.json(); + this.log.debug(`(refreshRoomPriority) roompriority: ${JSON.stringify(roompriority)}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} Priority: ${JSON.stringify(roompriority)}`); } } @@ -516,38 +562,37 @@ export class Thermostats { */ async pushChanges(): Promise { try { - const payload = {} as settings.payload; - + const payload = {} as payload; // Only include mode on certain models switch (this.device.deviceModel) { case 'Unknown': - this.debugLog(`Thermostat: ${this.accessory.displayName} didn't send TargetHeatingCoolingState,` + ` Model: ${this.device.deviceModel}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} didn't send TargetHeatingCoolingState,` + ` Model: ${this.device.deviceModel}`); break; default: payload.mode = this.resideoMode[Number(this.TargetHeatingCoolingState)]; - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName} send TargetHeatingCoolingState` + - ` mode: ${this.resideoMode[Number(this.TargetHeatingCoolingState)]}`, + ` mode: ${this.resideoMode[Number(this.TargetHeatingCoolingState)]}`, ); } // Only include thermostatSetpointStatus on certain models switch (this.device.deviceModel) { case 'Round': - this.debugLog(`Thermostat: ${this.accessory.displayName} didn't send thermostatSetpointStatus,` + ` Model: ${this.device.deviceModel}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} didn't send thermostatSetpointStatus,` + ` Model: ${this.device.deviceModel}`); break; default: this.pushChangesthermostatSetpointStatus(); payload.thermostatSetpointStatus = this.thermostatSetpointStatus; if (this.thermostatSetpointStatus === 'TemporaryHold') { - this.warnLog( + this.log.warn( `Thermostat: ${this.accessory.displayName} send thermostatSetpointStatus: ` + - `${payload.thermostatSetpointStatus}, Model: ${this.device.deviceModel}`, + `${payload.thermostatSetpointStatus}, Model: ${this.device.deviceModel}`, ); } else { - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName} send thermostatSetpointStatus: ` + - `${payload.thermostatSetpointStatus}, Model: ${this.device.deviceModel}`, + `${payload.thermostatSetpointStatus}, Model: ${this.device.deviceModel}`, ); } } @@ -556,39 +601,39 @@ export class Thermostats { case 'Round': case 'D6': if (this.deviceLogging.includes('debug')) { - this.warnLog(`Thermostat: ${this.accessory.displayName} set autoChangeoverActive, Model: ${this.device.deviceModel}`); + this.log.warn(`Thermostat: ${this.accessory.displayName} set autoChangeoverActive, Model: ${this.device.deviceModel}`); } // for Round the 'Auto' feature is enabled via the special mode so only flip this bit when // the heating/cooling state is set to `Auto - if (this.TargetHeatingCoolingState === this.platform.Characteristic.TargetHeatingCoolingState.AUTO) { + if (this.TargetHeatingCoolingState === this.hap.Characteristic.TargetHeatingCoolingState.AUTO) { payload.autoChangeoverActive = true; - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName} Heating/Cooling state set to Auto for` + - ` Model: ${this.device.deviceModel}, Force autoChangeoverActive: ${payload.autoChangeoverActive}`, + ` Model: ${this.device.deviceModel}, Force autoChangeoverActive: ${payload.autoChangeoverActive}`, ); } else { payload.autoChangeoverActive = this.device.changeableValues?.autoChangeoverActive; - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName} Heating/cooling state not set to Auto for` + - ` Model: ${this.device.deviceModel}, Using device setting` + - ` autoChangeoverActive: ${this.device.changeableValues!.autoChangeoverActive}`, + ` Model: ${this.device.deviceModel}, Using device setting` + + ` autoChangeoverActive: ${this.device.changeableValues!.autoChangeoverActive}`, ); } break; case 'Unknown': - this.debugLog(`Thermostat: ${this.accessory.displayName} do not send autoChangeoverActive,` + ` Model: ${this.device.deviceModel}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} do not send autoChangeoverActive,` + ` Model: ${this.device.deviceModel}`); break; default: payload.autoChangeoverActive = this.device.changeableValues!.autoChangeoverActive; - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName} set autoChangeoverActive to ` + - `${this.device.changeableValues!.autoChangeoverActive} for Model: ${this.device.deviceModel}`, + `${this.device.changeableValues!.autoChangeoverActive} for Model: ${this.device.deviceModel}`, ); } switch (this.device.deviceModel) { case 'Unknown': - this.errorLog(superStringify(this.device)); + this.log.error(JSON.stringify(this.device)); payload.thermostatSetpoint = this.toFahrenheit(Number(this.TargetTemperature)); switch (this.device.units) { case 'Fahrenheit': @@ -598,61 +643,72 @@ export class Thermostats { payload.unit = 'Celsius'; break; } - this.infoLog( + this.log.info( `Thermostat: ${this.accessory.displayName} sent request to Resideo API thermostatSetpoint:` + - ` ${payload.thermostatSetpoint}, unit: ${payload.unit}`, + ` ${payload.thermostatSetpoint}, unit: ${payload.unit}`, ); break; default: // Set the heat and cool set point value based on the selected mode switch (this.TargetHeatingCoolingState) { - case this.platform.Characteristic.TargetHeatingCoolingState.HEAT: + case this.hap.Characteristic.TargetHeatingCoolingState.HEAT: payload.heatSetpoint = this.toFahrenheit(Number(this.TargetTemperature)); payload.coolSetpoint = this.toFahrenheit(Number(this.CoolingThresholdTemperature)); - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName} TargetHeatingCoolingState (HEAT): ${this.TargetHeatingCoolingState},` + - ` TargetTemperature: ${this.toFahrenheit(Number(this.TargetTemperature))} heatSetpoint,` + - ` CoolingThresholdTemperature: ${this.toFahrenheit(Number(this.CoolingThresholdTemperature))} coolSetpoint`, + ` TargetTemperature: ${this.toFahrenheit(Number(this.TargetTemperature))} heatSetpoint,` + + ` CoolingThresholdTemperature: ${this.toFahrenheit(Number(this.CoolingThresholdTemperature))} coolSetpoint`, ); break; - case this.platform.Characteristic.TargetHeatingCoolingState.COOL: + case this.hap.Characteristic.TargetHeatingCoolingState.COOL: payload.coolSetpoint = this.toFahrenheit(Number(this.TargetTemperature)); payload.heatSetpoint = this.toFahrenheit(Number(this.HeatingThresholdTemperature)); - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName} TargetHeatingCoolingState (COOL): ${this.TargetHeatingCoolingState},` + - ` TargetTemperature: ${this.toFahrenheit(Number(this.TargetTemperature))} coolSetpoint,` + - ` CoolingThresholdTemperature: ${this.toFahrenheit(Number(this.HeatingThresholdTemperature))} heatSetpoint`, + ` TargetTemperature: ${this.toFahrenheit(Number(this.TargetTemperature))} coolSetpoint,` + + ` CoolingThresholdTemperature: ${this.toFahrenheit(Number(this.HeatingThresholdTemperature))} heatSetpoint`, ); break; - case this.platform.Characteristic.TargetHeatingCoolingState.AUTO: + case this.hap.Characteristic.TargetHeatingCoolingState.AUTO: payload.coolSetpoint = this.toFahrenheit(Number(this.CoolingThresholdTemperature)); payload.heatSetpoint = this.toFahrenheit(Number(this.HeatingThresholdTemperature)); - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName} TargetHeatingCoolingState (AUTO): ${this.TargetHeatingCoolingState},` + - ` CoolingThresholdTemperature: ${this.toFahrenheit(Number(this.CoolingThresholdTemperature))} coolSetpoint,` + - ` HeatingThresholdTemperature: ${this.toFahrenheit(Number(this.HeatingThresholdTemperature))} heatSetpoint`, + ` CoolingThresholdTemperature: ${this.toFahrenheit(Number(this.CoolingThresholdTemperature))} coolSetpoint,` + + ` HeatingThresholdTemperature: ${this.toFahrenheit(Number(this.HeatingThresholdTemperature))} heatSetpoint`, ); break; default: payload.coolSetpoint = this.toFahrenheit(Number(this.CoolingThresholdTemperature)); payload.heatSetpoint = this.toFahrenheit(Number(this.HeatingThresholdTemperature)); - this.debugLog( + this.log.debug( `Thermostat: ${this.accessory.displayName} TargetHeatingCoolingState (OFF): ${this.TargetHeatingCoolingState},` + - ` CoolingThresholdTemperature: ${this.toFahrenheit(Number(this.CoolingThresholdTemperature))} coolSetpoint,` + - ` HeatingThresholdTemperature: ${this.toFahrenheit(Number(this.HeatingThresholdTemperature))} heatSetpoint`, + ` CoolingThresholdTemperature: ${this.toFahrenheit(Number(this.CoolingThresholdTemperature))} coolSetpoint,` + + ` HeatingThresholdTemperature: ${this.toFahrenheit(Number(this.HeatingThresholdTemperature))} heatSetpoint`, ); } - this.infoLog(`Room Sensor Thermostat: ${this.accessory.displayName} set request (${superStringify(payload)}) to Resideo API.`); + this.log.info(`Room Sensor Thermostat: ${this.accessory.displayName} set request (${JSON.stringify(payload)}) to Resideo API.`); } // Attempt to make the API request - await this.platform.axios.post(`${settings.DeviceURL}/thermostats/${this.device.deviceID}`, payload, { - params: { - locationId: this.locationId, + const { statusCode } = await request(`${DeviceURL}/thermostats/${this.device.deviceID}`, { + method: 'POST', + body: JSON.stringify(payload), + query: { + 'locationId': this.locationId, + 'apikey': this.config.credentials?.consumerKey, + }, + headers: { + 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, + 'Content-Type': 'application/json', }, }); - this.debugLog(`Thermostat: ${this.accessory.displayName} pushChanges: ${superStringify(payload)}`); + const action = 'pushChanges'; + await this.statusCode(statusCode, action); + this.log.debug(`Thermostat: ${this.accessory.displayName} pushChanges: ${JSON.stringify(payload)}`); + await this.parseStatus(); + await this.updateHomeKitCharacteristics(); } catch (e: any) { this.action = 'pushChanges'; this.resideoAPIError(e); @@ -662,11 +718,11 @@ export class Thermostats { async pushChangesthermostatSetpointStatus(): Promise { if (this.thermostatSetpointStatus) { - this.debugLog(`Thermostat: ${this.accessory.displayName} thermostatSetpointStatus config set to ` + `${this.thermostatSetpointStatus}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} thermostatSetpointStatus config set to ` + `${this.thermostatSetpointStatus}`); } else { this.thermostatSetpointStatus = 'PermanentHold'; this.accessory.context.thermostatSetpointStatus = this.thermostatSetpointStatus; - this.debugLog(`Thermostat: ${this.accessory.displayName} thermostatSetpointStatus config not set`); + this.log.debug(`Thermostat: ${this.accessory.displayName} thermostatSetpointStatus config not set`); } } @@ -674,8 +730,8 @@ export class Thermostats { * Pushes the requested changes for Room Priority to the Resideo API */ async pushRoomChanges(): Promise { - this.debugLog(`Thermostat Room Priority for ${this.accessory.displayName} - Current Room: ${superStringify(this.roompriority.currentPriority.selectedRooms)}, + this.log.debug(`Thermostat Room Priority for ${this.accessory.displayName} + Current Room: ${JSON.stringify(this.roompriority.currentPriority.selectedRooms)}, Changing Room: [${this.device.inBuiltSensorState!.roomId}]`); if (`[${this.device.inBuiltSensorState!.roomId}]` !== `[${this.roompriority.currentPriority.selectedRooms}]`) { const payload = { @@ -696,25 +752,34 @@ export class Thermostats { */ if (this.device.thermostat?.roompriority?.deviceType === 'Thermostat') { if (this.device.priorityType === 'FollowMe') { - this.infoLog( + this.log.info( `Sending request for ${this.accessory.displayName} to Resideo API Priority Type:` + - ` ${this.device.priorityType}, Built-in Occupancy Sensor(s) Will be used to set Priority Automatically`, + ` ${this.device.priorityType}, Built-in Occupancy Sensor(s) Will be used to set Priority Automatically`, ); } else if (this.device.priorityType === 'WholeHouse') { - this.infoLog(`Sending request for ${this.accessory.displayName} to Resideo API Priority Type:` + ` ${this.device.priorityType}`); + this.log.info(`Sending request for ${this.accessory.displayName} to Resideo API Priority Type:` + ` ${this.device.priorityType}`); } else if (this.device.priorityType === 'PickARoom') { - this.infoLog( + this.log.info( `Sending request for ${this.accessory.displayName} to Resideo API Room Priority:` + - ` ${this.device.inBuiltSensorState!.roomName}, Priority Type: ${this.device.thermostat?.roompriority.priorityType}`, + ` ${this.device.inBuiltSensorState!.roomName}, Priority Type: ${this.device.thermostat?.roompriority.priorityType}`, ); } // Make the API request - await this.platform.axios.put(`${settings.DeviceURL}/thermostats/${this.device.deviceID}/priority`, payload, { - params: { - locationId: this.locationId, + const { statusCode } = await request(`${DeviceURL}/thermostats/${this.device.deviceID}/priority`, { + method: 'PUT', + body: JSON.stringify(payload), + query: { + 'locationId': this.locationId, + 'apikey': this.config.credentials?.consumerKey, + }, + headers: { + 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, + 'Content-Type': 'application/json', }, }); - this.debugLog(`Thermostat: ${this.accessory.displayName} pushRoomChanges: ${superStringify(payload)}`); + const action = 'pushRoomChanges'; + await this.statusCode(statusCode, action); + this.log.debug(`Thermostat: ${this.accessory.displayName} pushRoomChanges: ${JSON.stringify(payload)}`); } } } @@ -724,93 +789,93 @@ export class Thermostats { */ async updateHomeKitCharacteristics(): Promise { if (this.TemperatureDisplayUnits === undefined) { - this.debugLog(`Thermostat: ${this.accessory.displayName} TemperatureDisplayUnits: ${this.TemperatureDisplayUnits}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} TemperatureDisplayUnits: ${this.TemperatureDisplayUnits}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits, this.TemperatureDisplayUnits); - this.debugLog(`Thermostat: ${this.accessory.displayName} updateCharacteristic TemperatureDisplayUnits: ${this.TemperatureDisplayUnits}`); + this.service.updateCharacteristic(this.hap.Characteristic.TemperatureDisplayUnits, this.TemperatureDisplayUnits); + this.log.debug(`Thermostat: ${this.accessory.displayName} updateCharacteristic TemperatureDisplayUnits: ${this.TemperatureDisplayUnits}`); } if (this.CurrentTemperature === undefined) { - this.debugLog(`Thermostat: ${this.accessory.displayName} CurrentTemperature: ${this.CurrentTemperature}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} CurrentTemperature: ${this.CurrentTemperature}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.CurrentTemperature); - this.debugLog(`Thermostat: ${this.accessory.displayName} updateCharacteristic CurrentTemperature: ${this.CurrentTemperature}`); + this.service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, this.CurrentTemperature); + this.log.debug(`Thermostat: ${this.accessory.displayName} updateCharacteristic CurrentTemperature: ${this.CurrentTemperature}`); } if (!this.device.indoorHumidity || this.device.thermostat?.hide_humidity || this.CurrentRelativeHumidity === undefined) { - this.debugLog(`Thermostat: ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`); } else { - this.humidityService!.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, this.CurrentRelativeHumidity); - this.debugLog(`Thermostat: ${this.accessory.displayName} updateCharacteristic CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`); + this.humidityService!.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, this.CurrentRelativeHumidity); + this.log.debug(`Thermostat: ${this.accessory.displayName} updateCharacteristic CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`); } if (this.TargetTemperature === undefined) { - this.debugLog(`Thermostat: ${this.accessory.displayName} TargetTemperature: ${this.TargetTemperature}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} TargetTemperature: ${this.TargetTemperature}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.TargetTemperature, this.TargetTemperature); - this.debugLog(`Thermostat: ${this.accessory.displayName} updateCharacteristic TargetTemperature: ${this.TargetTemperature}`); + this.service.updateCharacteristic(this.hap.Characteristic.TargetTemperature, this.TargetTemperature); + this.log.debug(`Thermostat: ${this.accessory.displayName} updateCharacteristic TargetTemperature: ${this.TargetTemperature}`); } if (this.HeatingThresholdTemperature === undefined) { - this.debugLog(`Thermostat: ${this.accessory.displayName} HeatingThresholdTemperature: ${this.HeatingThresholdTemperature}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} HeatingThresholdTemperature: ${this.HeatingThresholdTemperature}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature, this.HeatingThresholdTemperature); - this.debugLog( + this.service.updateCharacteristic(this.hap.Characteristic.HeatingThresholdTemperature, this.HeatingThresholdTemperature); + this.log.debug( `Thermostat: ${this.accessory.displayName} updateCharacteristic` + ` HeatingThresholdTemperature: ${this.HeatingThresholdTemperature}`, ); } if (this.CoolingThresholdTemperature === undefined) { - this.debugLog(`Thermostat: ${this.accessory.displayName} CoolingThresholdTemperature: ${this.CoolingThresholdTemperature}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} CoolingThresholdTemperature: ${this.CoolingThresholdTemperature}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.CoolingThresholdTemperature, this.CoolingThresholdTemperature); - this.debugLog( + this.service.updateCharacteristic(this.hap.Characteristic.CoolingThresholdTemperature, this.CoolingThresholdTemperature); + this.log.debug( `Thermostat: ${this.accessory.displayName} updateCharacteristic` + ` CoolingThresholdTemperature: ${this.CoolingThresholdTemperature}`, ); } if (this.TargetHeatingCoolingState === undefined) { - this.debugLog(`Thermostat: ${this.accessory.displayName} TargetHeatingCoolingState: ${this.TargetHeatingCoolingState}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} TargetHeatingCoolingState: ${this.TargetHeatingCoolingState}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState, this.TargetHeatingCoolingState); - this.debugLog( + this.service.updateCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState, this.TargetHeatingCoolingState); + this.log.debug( `Thermostat: ${this.accessory.displayName} updateCharacteristic` + ` TargetHeatingCoolingState: ${this.TargetHeatingCoolingState}`, ); } if (this.CurrentHeatingCoolingState === undefined) { - this.debugLog(`Thermostat: ${this.accessory.displayName} CurrentHeatingCoolingState: ${this.CurrentHeatingCoolingState}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} CurrentHeatingCoolingState: ${this.CurrentHeatingCoolingState}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState, this.CurrentHeatingCoolingState); - this.debugLog( + this.service.updateCharacteristic(this.hap.Characteristic.CurrentHeatingCoolingState, this.CurrentHeatingCoolingState); + this.log.debug( `Thermostat: ${this.accessory.displayName} updateCharacteristic` + ` CurrentHeatingCoolingState: ${this.TargetHeatingCoolingState}`, ); } if (this.device.settings?.fan && !this.device.thermostat?.hide_fan) { if (this.TargetFanState === undefined) { - this.debugLog(`Thermostat Fan: ${this.accessory.displayName} TargetFanState: ${this.TargetFanState}`); + this.log.debug(`Thermostat Fan: ${this.accessory.displayName} TargetFanState: ${this.TargetFanState}`); } else { - this.fanService?.updateCharacteristic(this.platform.Characteristic.TargetFanState, this.TargetFanState); - this.debugLog(`Thermostat Fan: ${this.accessory.displayName} updateCharacteristic TargetFanState: ${this.TargetFanState}`); + this.fanService?.updateCharacteristic(this.hap.Characteristic.TargetFanState, this.TargetFanState); + this.log.debug(`Thermostat Fan: ${this.accessory.displayName} updateCharacteristic TargetFanState: ${this.TargetFanState}`); } if (this.Active === undefined) { - this.debugLog(`Thermostat Fan: ${this.accessory.displayName} Active: ${this.Active}`); + this.log.debug(`Thermostat Fan: ${this.accessory.displayName} Active: ${this.Active}`); } else { - this.fanService?.updateCharacteristic(this.platform.Characteristic.Active, this.Active); - this.debugLog(`Thermostat Fan: ${this.accessory.displayName} updateCharacteristic Active: ${this.Active}`); + this.fanService?.updateCharacteristic(this.hap.Characteristic.Active, this.Active); + this.log.debug(`Thermostat Fan: ${this.accessory.displayName} updateCharacteristic Active: ${this.Active}`); } } } async apiError(e: any): Promise { - this.service.updateCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits, e); - this.service.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, e); + this.service.updateCharacteristic(this.hap.Characteristic.TemperatureDisplayUnits, e); + this.service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e); if (this.device.indoorHumidity && !this.device.thermostat?.hide_humidity) { - this.humidityService!.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, e); + this.humidityService!.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, e); } - this.service.updateCharacteristic(this.platform.Characteristic.TargetTemperature, e); - this.service.updateCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature, e); - this.service.updateCharacteristic(this.platform.Characteristic.CoolingThresholdTemperature, e); - this.service.updateCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState, e); - this.service.updateCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState, e); + this.service.updateCharacteristic(this.hap.Characteristic.TargetTemperature, e); + this.service.updateCharacteristic(this.hap.Characteristic.HeatingThresholdTemperature, e); + this.service.updateCharacteristic(this.hap.Characteristic.CoolingThresholdTemperature, e); + this.service.updateCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState, e); + this.service.updateCharacteristic(this.hap.Characteristic.CurrentHeatingCoolingState, e); if (this.device.settings?.fan && !this.device.thermostat?.hide_fan) { - this.fanService?.updateCharacteristic(this.platform.Characteristic.TargetFanState, e); - this.fanService?.updateCharacteristic(this.platform.Characteristic.Active, e); + this.fanService?.updateCharacteristic(this.hap.Characteristic.TargetFanState, e); + this.fanService?.updateCharacteristic(this.hap.Characteristic.Active, e); } - //throw new this.platform.api.hap.HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE); + //throw new this.api.hap.HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE); } async resideoAPIError(e: any): Promise { @@ -858,55 +923,82 @@ export class Thermostats { } } if (e.message.includes('400')) { - this.errorLog(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Bad Request`); - this.debugLog('The client has issued an invalid request. This is commonly used to specify validation errors in a request payload.'); + this.log.error(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Bad Request`); + this.log.debug('The client has issued an invalid request. This is commonly used to specify validation errors in a request payload.'); } else if (e.message.includes('401')) { - this.errorLog(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Unauthorized Request`); - this.debugLog('Authorization for the API is required, but the request has not been authenticated.'); + this.log.error(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Unauthorized Request`); + this.log.debug('Authorization for the API is required, but the request has not been authenticated.'); } else if (e.message.includes('403')) { - this.errorLog(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Forbidden Request`); - this.debugLog('The request has been authenticated but does not have appropriate permissions, or a requested resource is not found.'); + this.log.error(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Forbidden Request`); + this.log.debug('The request has been authenticated but does not have appropriate permissions, or a requested resource is not found.'); } else if (e.message.includes('404')) { - this.errorLog(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Requst Not Found`); - this.debugLog('Specifies the requested path does not exist.'); + this.log.error(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Requst Not Found`); + this.log.debug('Specifies the requested path does not exist.'); } else if (e.message.includes('406')) { - this.errorLog(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Request Not Acceptable`); - this.debugLog('The client has requested a MIME type via the Accept header for a value not supported by the server.'); + this.log.error(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Request Not Acceptable`); + this.log.debug('The client has requested a MIME type via the Accept header for a value not supported by the server.'); } else if (e.message.includes('415')) { - this.errorLog(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Unsupported Requst Header`); - this.debugLog('The client has defined a contentType header that is not supported by the server.'); + this.log.error(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Unsupported Requst Header`); + this.log.debug('The client has defined a contentType header that is not supported by the server.'); } else if (e.message.includes('422')) { - this.errorLog(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Unprocessable Entity`); - this.debugLog( + this.log.error(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Unprocessable Entity`); + this.log.debug( 'The client has made a valid request, but the server cannot process it.' + - ' This is often used for APIs for which certain limits have been exceeded.', + ' This is often used for APIs for which certain limits have been exceeded.', ); } else if (e.message.includes('429')) { - this.errorLog(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Too Many Requests`); - this.debugLog('The client has exceeded the number of requests allowed for a given time window.'); + this.log.error(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Too Many Requests`); + this.log.debug('The client has exceeded the number of requests allowed for a given time window.'); } else if (e.message.includes('500')) { - this.errorLog(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Internal Server Error`); - this.debugLog('An unexpected error on the SmartThings servers has occurred. These errors should be rare.'); + this.log.error(`Thermostat: ${this.accessory.displayName} failed to ${this.action}, Internal Server Error`); + this.log.debug('An unexpected error on the SmartThings servers has occurred. These errors should be rare.'); } else { - this.errorLog(`Thermostat: ${this.accessory.displayName} failed to ${this.action},`); + this.log.error(`Thermostat: ${this.accessory.displayName} failed to ${this.action},`); } if (this.deviceLogging.includes('debug')) { - this.errorLog(`Thermostat: ${this.accessory.displayName} failed to pushChanges, Error Message: ${superStringify(e.message)}`); + this.log.error(`Thermostat: ${this.accessory.displayName} failed to pushChanges, Error Message: ${JSON.stringify(e.message)}`); + } + } + + async statusCode(statusCode: number, action: string): Promise { + switch (statusCode) { + case 200: + this.log.debug(`${this.device.deviceClass}: ${this.accessory.displayName} Standard Response, statusCode: ${statusCode}, Action: ${action}`); + break; + case 400: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Bad Request, statusCode: ${statusCode}, Action: ${action}`); + break; + case 401: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Unauthorized, statusCode: ${statusCode}, Action: ${action}`); + break; + case 404: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Not Found, statusCode: ${statusCode}, Action: ${action}`); + break; + case 429: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Too Many Requests, statusCode: ${statusCode}, Action: ${action}`); + break; + case 500: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Internal Server Error (Meater Server), statusCode: ${statusCode}, ` + + `Action: ${action}`); + break; + default: + this.log.info(`${this.device.deviceClass}: ${this.accessory.displayName} Unknown statusCode: ${statusCode}, ` + + `Action: ${action}, Report Bugs Here: https://bit.ly/homebridge-resideo-bug-report`); } } async setTargetHeatingCoolingState(value: CharacteristicValue): Promise { - this.debugLog(`Thermostat: ${this.accessory.displayName} Set TargetHeatingCoolingState: ${value}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} Set TargetHeatingCoolingState: ${value}`); this.TargetHeatingCoolingState = value; // Set the TargetTemperature value based on the selected mode - if (this.TargetHeatingCoolingState === this.platform.Characteristic.TargetHeatingCoolingState.HEAT) { + if (this.TargetHeatingCoolingState === this.hap.Characteristic.TargetHeatingCoolingState.HEAT) { this.TargetTemperature = this.toCelsius(this.device.changeableValues!.heatSetpoint); } else { this.TargetTemperature = this.toCelsius(this.device.changeableValues!.coolSetpoint); } - this.service.updateCharacteristic(this.platform.Characteristic.TargetTemperature, this.TargetTemperature); + this.service.updateCharacteristic(this.hap.Characteristic.TargetTemperature, this.TargetTemperature); if (this.device.thermostat?.roompriority?.deviceType === 'Thermostat' && this.device.deviceModel === 'T9-T10') { this.doRoomUpdate.next(); } @@ -916,30 +1008,30 @@ export class Thermostats { } async setHeatingThresholdTemperature(value: CharacteristicValue): Promise { - this.debugLog(`Thermostat: ${this.accessory.displayName} Set HeatingThresholdTemperature: ${value}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} Set HeatingThresholdTemperature: ${value}`); this.HeatingThresholdTemperature = value; this.doThermostatUpdate.next(); } async setCoolingThresholdTemperature(value: CharacteristicValue): Promise { - this.debugLog(`Thermostat: ${this.accessory.displayName} Set CoolingThresholdTemperature: ${value}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} Set CoolingThresholdTemperature: ${value}`); this.CoolingThresholdTemperature = value; this.doThermostatUpdate.next(); } async setTargetTemperature(value: CharacteristicValue): Promise { - this.debugLog(`Thermostat: ${this.accessory.displayName} Set TargetTemperature: ${value}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} Set TargetTemperature: ${value}`); this.TargetTemperature = value; this.doThermostatUpdate.next(); } async setTemperatureDisplayUnits(value: CharacteristicValue): Promise { - this.debugLog(`Thermostat: ${this.accessory.displayName} Set TemperatureDisplayUnits: ${value}`); - this.warnLog('Changing the Hardware Display Units from HomeKit is not supported.'); + this.log.debug(`Thermostat: ${this.accessory.displayName} Set TemperatureDisplayUnits: ${value}`); + this.log.warn('Changing the Hardware Display Units from HomeKit is not supported.'); // change the temp units back to the one the Resideo API said the thermostat was set to setTimeout(() => { - this.service.updateCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits, this.TemperatureDisplayUnits); + this.service.updateCharacteristic(this.hap.Characteristic.TemperatureDisplayUnits, this.TemperatureDisplayUnits); }, 100); } @@ -947,10 +1039,10 @@ export class Thermostats { * Handle requests to get the current value of the "Programmable Switch Event" characteristic */ handleProgrammableSwitchEventGet() { - this.debugLog('Triggered GET ProgrammableSwitchEvent'); + this.log.debug('Triggered GET ProgrammableSwitchEvent'); // set this to a valid value for ProgrammableSwitchEvent - const currentValue = this.platform.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS; + const currentValue = this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS; return currentValue; } @@ -960,7 +1052,7 @@ export class Thermostats { * Handle requests to get the current value of the "Programmable Switch Output State" characteristic */ handleProgrammableSwitchOutputStateGet() { - this.debugLog('Triggered GET ProgrammableSwitchOutputState'); + this.log.debug('Triggered GET ProgrammableSwitchOutputState'); // set this to a valid value for ProgrammableSwitchOutputState const currentValue = 1; @@ -972,14 +1064,14 @@ export class Thermostats { * Handle requests to set the "Programmable Switch Output State" characteristic */ handleProgrammableSwitchOutputStateSet(value) { - this.debugLog('Triggered SET ProgrammableSwitchOutputState:', value); + this.log.debug('Triggered SET ProgrammableSwitchOutputState:', value); } /** * Converts the value to celsius if the temperature units are in Fahrenheit */ toCelsius(value: number): number { - if (this.TemperatureDisplayUnits === this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS) { + if (this.TemperatureDisplayUnits === this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS) { return value; } @@ -991,7 +1083,7 @@ export class Thermostats { * Converts the value to fahrenheit if the temperature units are in Fahrenheit */ toFahrenheit(value: number): number { - if (this.TemperatureDisplayUnits === this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS) { + if (this.TemperatureDisplayUnits === this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS) { return value; } @@ -1006,36 +1098,45 @@ export class Thermostats { mode: 'Auto', // default to Auto }; if (this.device.settings?.fan && !this.device.thermostat?.hide_fan) { - this.debugLog(`Thermostat: ${this.accessory.displayName} TargetFanState: ${this.TargetFanState}, Active: ${this.Active}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} TargetFanState: ${this.TargetFanState}, Active: ${this.Active}`); - if (this.TargetFanState === this.platform.Characteristic.TargetFanState.AUTO) { + if (this.TargetFanState === this.hap.Characteristic.TargetFanState.AUTO) { payload = { mode: 'Auto', }; } else if ( - this.TargetFanState === this.platform.Characteristic.TargetFanState.MANUAL && - this.Active === this.platform.Characteristic.Active.ACTIVE + this.TargetFanState === this.hap.Characteristic.TargetFanState.MANUAL && + this.Active === this.hap.Characteristic.Active.ACTIVE ) { payload = { mode: 'On', }; } else if ( - this.TargetFanState === this.platform.Characteristic.TargetFanState.MANUAL && - this.Active === this.platform.Characteristic.Active.INACTIVE + this.TargetFanState === this.hap.Characteristic.TargetFanState.MANUAL && + this.Active === this.hap.Characteristic.Active.INACTIVE ) { payload = { mode: 'Circulate', }; } - this.infoLog(`Sending request for ${this.accessory.displayName} to Resideo API Fan Mode: ${payload.mode}`); + this.log.info(`Sending request for ${this.accessory.displayName} to Resideo API Fan Mode: ${payload.mode}`); // Make the API request - await this.platform.axios.post(`${settings.DeviceURL}/thermostats/${this.device.deviceID}/fan`, payload, { - params: { - locationId: this.locationId, + const { statusCode } = await request(`${DeviceURL}/thermostats/${this.device.deviceID}/fan`, { + method: 'PUT', + body: JSON.stringify(payload), + query: { + 'locationId': this.locationId, + 'apikey': this.config.credentials?.consumerKey, + }, + headers: { + 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, + 'Content-Type': 'application/json', }, }); - this.debugLog(`Thermostat: ${this.accessory.displayName} pushChanges: ${superStringify(payload)}`); + const action = 'pushFanChanges'; + await this.statusCode(statusCode, action); + this.log.debug(`Thermostat: ${this.accessory.displayName} pushChanges: ${JSON.stringify(payload)}`); } } @@ -1043,80 +1144,38 @@ export class Thermostats { * Updates the status for each of the HomeKit Characteristics */ async setActive(value: CharacteristicValue): Promise { - this.debugLog(`Thermostat: ${this.accessory.displayName} Set Active: ${value}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} Set Active: ${value}`); this.Active = value; this.doFanUpdate.next(); } async setTargetFanState(value: CharacteristicValue): Promise { - this.debugLog(`Thermostat: ${this.accessory.displayName} Set TargetFanState: ${value}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} Set TargetFanState: ${value}`); this.TargetFanState = value; this.doFanUpdate.next(); } TargetState(): number[] { - this.debugLog(`Thermostat: ${this.accessory.displayName} allowedModes: ${this.device.allowedModes}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} allowedModes: ${this.device.allowedModes}`); const TargetState = [4]; TargetState.pop(); if (this.device.allowedModes?.includes('Cool')) { - TargetState.push(this.platform.Characteristic.TargetHeatingCoolingState.COOL); + TargetState.push(this.hap.Characteristic.TargetHeatingCoolingState.COOL); } if (this.device.allowedModes?.includes('Heat')) { - TargetState.push(this.platform.Characteristic.TargetHeatingCoolingState.HEAT); + TargetState.push(this.hap.Characteristic.TargetHeatingCoolingState.HEAT); } if (this.device.allowedModes?.includes('Off')) { - TargetState.push(this.platform.Characteristic.TargetHeatingCoolingState.OFF); + TargetState.push(this.hap.Characteristic.TargetHeatingCoolingState.OFF); } if (this.device.allowedModes?.includes('Auto') || this.device.thermostat?.show_auto) { - TargetState.push(this.platform.Characteristic.TargetHeatingCoolingState.AUTO); + TargetState.push(this.hap.Characteristic.TargetHeatingCoolingState.AUTO); } - this.debugLog(`Thermostat: ${this.accessory.displayName} Only Show These Modes: ${superStringify(TargetState)}`); + this.log.debug(`Thermostat: ${this.accessory.displayName} Only Show These Modes: ${JSON.stringify(TargetState)}`); return TargetState; } - async config(device: settings.device & settings.devicesConfig): Promise { - let config = {}; - if (device.thermostat) { - config = device.thermostat; - } - if (device.logging !== undefined) { - config['logging'] = device.logging; - } - if (device.refreshRate !== undefined) { - config['refreshRate'] = device.refreshRate; - } - if (Object.entries(config).length !== 0) { - this.infoLog(`Thermostat: ${this.accessory.displayName} Config: ${superStringify(config)}`); - } - } - - async refreshRate(device: settings.device & settings.devicesConfig): Promise { - if (device.refreshRate) { - this.deviceRefreshRate = this.accessory.context.refreshRate = device.refreshRate; - this.debugLog(`Thermostat: ${this.accessory.displayName} Using Device Config refreshRate: ${this.deviceRefreshRate}`); - } else if (this.platform.config.options!.refreshRate) { - this.deviceRefreshRate = this.accessory.context.refreshRate = this.platform.config.options!.refreshRate; - this.debugLog(`Thermostat: ${this.accessory.displayName} Using Platform Config refreshRate: ${this.deviceRefreshRate}`); - } - } - - async logs(device: settings.device & settings.devicesConfig): Promise { - if (this.platform.debugMode) { - this.deviceLogging = this.accessory.context.logging = 'debugMode'; - this.debugLog(`Thermostat: ${this.accessory.displayName} Using Debug Mode Logging: ${this.deviceLogging}`); - } else if (device.logging) { - this.deviceLogging = this.accessory.context.logging = device.logging; - this.debugLog(`Thermostat: ${this.accessory.displayName} Using Device Config Logging: ${this.deviceLogging}`); - } else if (this.platform.config.options?.logging) { - this.deviceLogging = this.accessory.context.logging = this.platform.config.options?.logging; - this.debugLog(`Thermostat: ${this.accessory.displayName} Using Platform Config Logging: ${this.deviceLogging}`); - } else { - this.deviceLogging = this.accessory.context.logging = 'standard'; - this.debugLog(`Thermostat: ${this.accessory.displayName} Logging Not Set, Using: ${this.deviceLogging}`); - } - } - /** * Logging for Device */ @@ -1132,7 +1191,7 @@ export class Thermostats { } } - debugWarnLog({ log = [] }: { log?: any[] } = {}): void { + debugWarnLog({ log = [] }: { log?: any[]; } = {}): void { if (this.enablingDeviceLogging()) { if (this.deviceLogging?.includes('debug')) { this.platform.log.warn('[DEBUG]', String(...log)); diff --git a/src/devices/valve.ts b/src/devices/valve.ts index cb238908..58f6c670 100644 --- a/src/devices/valve.ts +++ b/src/devices/valve.ts @@ -1,9 +1,9 @@ -import { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import { request } from 'undici'; import { interval, Subject } from 'rxjs'; -import superStringify from 'super-stringify'; -import { skipWhile, take } from 'rxjs/operators'; -import { ResideoPlatform } from '../platform'; -import * as settings from '../settings'; +import { debounceTime, skipWhile, take, tap } from 'rxjs/operators'; +import { ResideoPlatform } from '../platform.js'; +import { API, HAP, CharacteristicValue, PlatformAccessory, Service, Logging } from 'homebridge'; +import { devicesConfig, DeviceURL, location, resideoDevice, payload, ResideoPlatformConfig } from '../settings.js'; /** * Platform Accessory @@ -11,6 +11,10 @@ import * as settings from '../settings'; * Each accessory may expose multiple services of different service types. */ export class Valve { + public readonly api: API; + public readonly log: Logging; + public readonly config!: ResideoPlatformConfig; + protected readonly hap: HAP; // Services service: Service; @@ -30,45 +34,67 @@ export class Valve { valvetype!: number; // Updates - SensorUpdateInProgress!: boolean; - doSensorUpdate!: Subject; + valveUpdateInProgress!: boolean; + doValveUpdate!: Subject; constructor( private readonly platform: ResideoPlatform, - private accessory: PlatformAccessory, - public readonly locationId: settings.location['locationID'], - public device: settings.device & settings.devicesConfig, + private readonly accessory: PlatformAccessory, + public readonly locationId: location['locationID'], + public device: resideoDevice & devicesConfig, ) { - this.logs(device); - this.refreshRate(device); - this.config(device); - this.valveType(device); + this.api = this.platform.api; + this.log = this.platform.log; + this.config = this.platform.config; + this.hap = this.api.hap; + + this.Active = accessory.context.StatusActive || this.hap.Characteristic.Active.ACTIVE; + this.InUse = accessory.context.StatusInUse || this.hap.Characteristic.InUse.NOT_IN_USE; + if (accessory.context.ValveType === undefined) { + switch (device.valve?.valveType) { + case 1: + this.valvetype = this.hap.Characteristic.ValveType.IRRIGATION; + break; + case 2: + this.valvetype = this.hap.Characteristic.ValveType.SHOWER_HEAD; + break; + case 3: + this.valvetype = this.hap.Characteristic.ValveType.WATER_FAUCET; + break; + default: + this.valvetype = this.hap.Characteristic.ValveType.GENERIC_VALVE; + } + } else { + this.valvetype = accessory.context.ValveType; + } + accessory.context.FirmwareRevision = 'v2.0.0'; + + this.deviceLogging = this.device.logging || this.config.options?.logging || 'standard'; + // this is subject we use to track when we need to POST changes to the Resideo API - this.doSensorUpdate = new Subject(); - this.SensorUpdateInProgress = false; + this.doValveUpdate = new Subject(); + this.valveUpdateInProgress = false; // set accessory information accessory - .getService(this.platform.Service.AccessoryInformation)! - .setCharacteristic(this.platform.Characteristic.Manufacturer, 'Resideo') - .setCharacteristic(this.platform.Characteristic.Model, device.deviceType) - .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceID) - .setCharacteristic(this.platform.Characteristic.FirmwareRevision, accessory.context.firmwareRevision) - .getCharacteristic(this.platform.Characteristic.FirmwareRevision) - .updateValue(accessory.context.firmwareRevision); + .getService(this.hap.Service.AccessoryInformation)! + .setCharacteristic(this.hap.Characteristic.Manufacturer, 'Resideo') + .setCharacteristic(this.hap.Characteristic.Model, device.deviceType) + .setCharacteristic(this.hap.Characteristic.SerialNumber, device.deviceID) + .setCharacteristic(this.hap.Characteristic.FirmwareRevision, accessory.context.firmwareRevision || 'v2.0.0'); // get the LightBulb service if it exists, otherwise create a new LightBulb service // you can create multiple services for each accessory - (this.service = this.accessory.getService(this.platform.Service.Valve) || this.accessory.addService(this.platform.Service.Valve)), - `${accessory.displayName}`; + (this.service = this.accessory.getService(this.hap.Service.Valve) + || this.accessory.addService(this.hap.Service.Valve)), `${accessory.displayName}`; // To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error, // when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id: - // this.accessory.getService('NAME') ?? this.accessory.addService(this.platform.Service.Lightbulb, 'NAME', 'USER_DEFINED_SUBTYPE'); + // this.accessory.getService('NAME') ?? this.accessory.addService(this.hap.Service.Lightbulb, 'NAME', 'USER_DEFINED_SUBTYPE'); // set the service name, this is what is displayed as the default name on the Home app // in this example we are using the name we stored in the `accessory.context` in the `discoverDevices` method. - this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.displayName); + this.service.setCharacteristic(this.hap.Characteristic.Name, accessory.displayName); // each service must implement at-minimum the "required characteristics" for the given service type // see https://developers.homebridge.io/#/service/ @@ -77,25 +103,50 @@ export class Valve { this.parseStatus(); // create handlers for required characteristics - this.service.getCharacteristic(this.platform.Characteristic.Active).onSet(this.setActive.bind(this)); + this.service.getCharacteristic(this.hap.Characteristic.Active).onSet(this.setActive.bind(this)); - this.service.getCharacteristic(this.platform.Characteristic.InUse) + this.service.getCharacteristic(this.hap.Characteristic.InUse) .onGet(() => { return this.InUse!; }); // Set Valve Type - this.service.setCharacteristic(this.platform.Characteristic.ValveType, this.valvetype); + this.service.setCharacteristic(this.hap.Characteristic.ValveType, this.valvetype); // Retrieve initial values and updateHomekit this.updateHomeKitCharacteristics(); // Start an update interval - interval(this.platform.config.options!.refreshRate! * 1000) - .pipe(skipWhile(() => this.SensorUpdateInProgress)) + interval(this.config.options!.refreshRate! * 1000) + .pipe(skipWhile(() => this.valveUpdateInProgress)) .subscribe(async () => { await this.refreshStatus(); }); + + // Watch for Lock change events + // We put in a debounce of 100ms so we don't make duplicate calls + this.doValveUpdate + .pipe( + tap(() => { + this.valveUpdateInProgress = true; + }), + debounceTime(this.config.options!.pushRate! * 1000), + ) + .subscribe(async () => { + try { + await this.pushChanges(); + } catch (e: any) { + this.log.error(`doValveUpdate pushChanges: ${JSON.stringify(e)}`); + } + // Refresh the status from the API + interval(this.deviceRefreshRate * 500) + .pipe(skipWhile(() => this.valveUpdateInProgress)) + .pipe(take(1)) + .subscribe(async () => { + await this.refreshStatus(); + }); + this.valveUpdateInProgress = false; + }); } /** @@ -104,16 +155,16 @@ export class Valve { async parseStatus(): Promise { // Active if (this.isAlive) { - this.Active = this.platform.Characteristic.Active.ACTIVE; + this.Active = this.hap.Characteristic.Active.ACTIVE; } else { - this.Active = this.platform.Characteristic.Active.INACTIVE; + this.Active = this.hap.Characteristic.Active.INACTIVE; } // InUse if (this.valveStatus === 'Open') { - this.InUse = this.platform.Characteristic.InUse.IN_USE; + this.InUse = this.hap.Characteristic.InUse.IN_USE; } else { - this.InUse = this.platform.Characteristic.InUse.NOT_IN_USE; + this.InUse = this.hap.Characteristic.InUse.NOT_IN_USE; } } @@ -122,17 +173,25 @@ export class Valve { */ async refreshStatus(): Promise { try { - const device: any = ( - await this.platform.axios.get(`${settings.DeviceURL}/waterLeakDetectors/${this.device.deviceID}`, { - params: { - locationId: this.locationId, - }, - }) - ).data; + const { body, statusCode } = await request(`${DeviceURL}/shutoffvalve/${this.device.deviceID}`, { + method: 'GET', + query: { + 'locationId': this.locationId, + 'apikey': this.config.credentials?.consumerKey, + }, + headers: { + 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, + 'Content-Type': 'application/json', + }, + }); + const action = 'refreshStatus'; + await this.statusCode(statusCode, action); + const device: any = await body.json(); + this.log.debug(`(refreshStatus) ${device.deviceClass}: ${JSON.stringify(device)}`); this.device = device; this.isAlive = device.isAlive; this.valveStatus = device.actuatorValve.valveStatus; - this.debugLog(`Valve: ${this.accessory.displayName} device: ${superStringify(this.device)}`); + this.log.debug(`Valve: ${this.accessory.displayName} device: ${JSON.stringify(this.device)}`); this.parseStatus(); this.updateHomeKitCharacteristics(); } catch (e: any) { @@ -142,21 +201,53 @@ export class Valve { } } + /** + * Pushes the requested changes to the August API + */ + async pushChanges(): Promise { + try { + const payload = {} as payload; + if (this.Active === this.hap.Characteristic.Active.ACTIVE) { + payload.state = 'open'; + } else { + payload.state = 'closed'; + } + const { statusCode } = await request(`${DeviceURL}/thermostats/${this.device.deviceID}`, { + method: 'POST', + body: JSON.stringify(payload), + query: { + 'locationId': this.locationId, + 'apikey': this.config.credentials?.consumerKey, + }, + headers: { + 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, + 'Content-Type': 'application/json', + }, + }); + const action = 'pushChanges'; + await this.statusCode(statusCode, action); + this.log.debug(`Thermostat: ${this.accessory.displayName} pushChanges: ${JSON.stringify(payload)}`); + } catch (e: any) { + this.log.error(`pushChanges: ${JSON.stringify(e)}`); + this.log.error(`Lock: ${this.accessory.displayName} failed pushChanges, Error Message: ${JSON.stringify(e.message)}`); + } + } + /** * Updates the status for each of the HomeKit Characteristics */ async updateHomeKitCharacteristics(): Promise { if (this.Active === undefined) { - this.debugLog(`Valve: ${this.accessory.displayName} Active: ${this.Active}`); + this.log.debug(`Valve: ${this.accessory.displayName} Active: ${this.Active}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.Active, this.Active); - this.debugLog(`Valve: ${this.accessory.displayName} updateCharacteristic Active: ${this.Active}`); + this.service.updateCharacteristic(this.hap.Characteristic.Active, this.Active); + this.log.debug(`Valve: ${this.accessory.displayName} updateCharacteristic Active: ${this.Active}`); } if (this.InUse === undefined) { - this.debugLog(`Valve: ${this.accessory.displayName} InUse: ${this.InUse}`); + this.log.debug(`Valve: ${this.accessory.displayName} InUse: ${this.InUse}`); } else { - this.service.updateCharacteristic(this.platform.Characteristic.InUse, this.InUse); - this.debugLog(`Valve: ${this.accessory.displayName} updateCharacteristic InUse: ${this.InUse}`); + this.service.updateCharacteristic(this.hap.Characteristic.InUse, this.InUse); + this.log.debug(`Valve: ${this.accessory.displayName} updateCharacteristic InUse: ${this.InUse}`); } } @@ -164,23 +255,25 @@ export class Valve { * Handle requests to set the "Active" characteristic */ setActive(value) { - this.debugLog('Triggered SET Active:', value); + this.log.debug(`Thermostat: ${this.accessory.displayName} Set Active: ${value}`); + this.Active = value; + this.doValveUpdate.next(); } /** * Handle requests to get the current value of the "In Use" characteristic */ handleInUseGet() { - this.debugLog('Triggered GET InUse'); + this.log.debug('Triggered GET InUse'); // set this to a valid value for InUse - const currentValue = this.platform.Characteristic.InUse.NOT_IN_USE; + const currentValue = this.hap.Characteristic.InUse.NOT_IN_USE; return currentValue; } async apiError(e: any): Promise { - this.service.updateCharacteristic(this.platform.Characteristic.Active, e); + this.service.updateCharacteristic(this.hap.Characteristic.Active, e); } async resideoAPIError(e: any): Promise { @@ -188,7 +281,7 @@ export class Valve { if (this.action === 'refreshStatus') { // Refresh the status from the API interval(5000) - .pipe(skipWhile(() => this.SensorUpdateInProgress)) + .pipe(skipWhile(() => this.valveUpdateInProgress)) .pipe(take(1)) .subscribe(async () => { await this.refreshStatus(); @@ -196,94 +289,67 @@ export class Valve { } } if (e.message.includes('400')) { - this.platform.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Bad Request`); - this.debugLog('The client has issued an invalid request. This is commonly used to specify validation errors in a request payload.'); + this.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Bad Request`); + this.log.debug('The client has issued an invalid request. This is commonly used to specify validation errors in a request payload.'); } else if (e.message.includes('401')) { - this.platform.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Unauthorized Request`); - this.debugLog('Authorization for the API is required, but the request has not been authenticated.'); + this.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Unauthorized Request`); + this.log.debug('Authorization for the API is required, but the request has not been authenticated.'); } else if (e.message.includes('403')) { - this.platform.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Forbidden Request`); - this.debugLog('The request has been authenticated but does not have appropriate permissions, or a requested resource is not found.'); + this.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Forbidden Request`); + this.log.debug('The request has been authenticated but does not have appropriate permissions, or a requested resource is not found.'); } else if (e.message.includes('404')) { - this.platform.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Requst Not Found`); - this.debugLog('Specifies the requested path does not exist.'); + this.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Requst Not Found`); + this.log.debug('Specifies the requested path does not exist.'); } else if (e.message.includes('406')) { - this.platform.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Request Not Acceptable`); - this.debugLog('The client has requested a MIME type via the Accept header for a value not supported by the server.'); + this.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Request Not Acceptable`); + this.log.debug('The client has requested a MIME type via the Accept header for a value not supported by the server.'); } else if (e.message.includes('415')) { - this.platform.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Unsupported Requst Header`); - this.debugLog('The client has defined a contentType header that is not supported by the server.'); + this.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Unsupported Requst Header`); + this.log.debug('The client has defined a contentType header that is not supported by the server.'); } else if (e.message.includes('422')) { - this.platform.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Unprocessable Entity`); - this.debugLog( + this.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Unprocessable Entity`); + this.log.debug( 'The client has made a valid request, but the server cannot process it.' + - ' This is often used for APIs for which certain limits have been exceeded.', + ' This is often used for APIs for which certain limits have been exceeded.', ); } else if (e.message.includes('429')) { - this.platform.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Too Many Requests`); - this.debugLog('The client has exceeded the number of requests allowed for a given time window.'); + this.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Too Many Requests`); + this.log.debug('The client has exceeded the number of requests allowed for a given time window.'); } else if (e.message.includes('500')) { - this.platform.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Internal Server Error`); - this.debugLog('An unexpected error on the SmartThings servers has occurred. These errors should be rare.'); + this.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action}, Internal Server Error`); + this.log.debug('An unexpected error on the SmartThings servers has occurred. These errors should be rare.'); } else { - this.platform.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action},`); + this.log.error(`Valve: ${this.accessory.displayName} failed to ${this.action},`); } if (this.deviceLogging.includes('debug')) { - this.platform.log.error(`Valve: ${this.accessory.displayName} failed to pushChanges, Error Message: ${superStringify(e.message)}`); + this.log.error(`Valve: ${this.accessory.displayName} failed to pushChanges, Error Message: ${JSON.stringify(e.message)}`); } } - async valveType(device: settings.device & settings.devicesConfig): Promise { - if (device.valve?.valveType === 1) { - this.valvetype = this.platform.Characteristic.ValveType.IRRIGATION; - } else if (device.valve?.valveType === 2){ - this.valvetype = this.platform.Characteristic.ValveType.SHOWER_HEAD; - } else if (device.valve?.valveType === 3) { - this.valvetype = this.platform.Characteristic.ValveType.WATER_FAUCET; - } else { - this.valvetype = this.platform.Characteristic.ValveType.GENERIC_VALVE; - } - } - - async config(device: settings.device & settings.devicesConfig): Promise { - let config = {}; - if (device.leaksensor) { - config = device.leaksensor; - } - if (device.logging !== undefined) { - config['logging'] = device.logging; - } - if (device.refreshRate !== undefined) { - config['refreshRate'] = device.refreshRate; - } - if (Object.entries(config).length !== 0) { - this.infoLog(`Valve: ${this.accessory.displayName} Config: ${superStringify(config)}`); - } - } - - async refreshRate(device: settings.device & settings.devicesConfig): Promise { - if (device.refreshRate) { - this.deviceRefreshRate = this.accessory.context.refreshRate = device.refreshRate; - this.debugLog(`Valve: ${this.accessory.displayName} Using Device Config refreshRate: ${this.deviceRefreshRate}`); - } else if (this.platform.config.options!.refreshRate) { - this.deviceRefreshRate = this.accessory.context.refreshRate = this.platform.config.options!.refreshRate; - this.debugLog(`Valve: ${this.accessory.displayName} Using Platform Config refreshRate: ${this.deviceRefreshRate}`); - } - } - - async logs(device: settings.device & settings.devicesConfig): Promise { - if (this.platform.debugMode) { - this.deviceLogging = this.accessory.context.logging = 'debugMode'; - this.debugLog(`Valve: ${this.accessory.displayName} Using Debug Mode Logging: ${this.deviceLogging}`); - } else if (device.logging) { - this.deviceLogging = this.accessory.context.logging = device.logging; - this.debugLog(`Valve: ${this.accessory.displayName} Using Device Config Logging: ${this.deviceLogging}`); - } else if (this.platform.config.options?.logging) { - this.deviceLogging = this.accessory.context.logging = this.platform.config.options?.logging; - this.debugLog(`Valve: ${this.accessory.displayName} Using Platform Config Logging: ${this.deviceLogging}`); - } else { - this.deviceLogging = this.accessory.context.logging = 'standard'; - this.debugLog(`Valve: ${this.accessory.displayName} Logging Not Set, Using: ${this.deviceLogging}`); + async statusCode(statusCode: number, action: string): Promise { + switch (statusCode) { + case 200: + this.log.debug(`${this.device.deviceClass}: ${this.accessory.displayName} Standard Response, statusCode: ${statusCode}, Action: ${action}`); + break; + case 400: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Bad Request, statusCode: ${statusCode}, Action: ${action}`); + break; + case 401: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Unauthorized, statusCode: ${statusCode}, Action: ${action}`); + break; + case 404: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Not Found, statusCode: ${statusCode}, Action: ${action}`); + break; + case 429: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Too Many Requests, statusCode: ${statusCode}, Action: ${action}`); + break; + case 500: + this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Internal Server Error (Meater Server), statusCode: ${statusCode}, ` + + `Action: ${action}`); + break; + default: + this.log.info(`${this.device.deviceClass}: ${this.accessory.displayName} Unknown statusCode: ${statusCode}, ` + + `Action: ${action}, Report Bugs Here: https://bit.ly/homebridge-resideo-bug-report`); } } @@ -302,7 +368,7 @@ export class Valve { } } - debugWarnLog(...log: any[]): void { + debugWarnLog({ log = [] }: { log?: any[]; } = {}): void { if (this.enablingDeviceLogging()) { if (this.deviceLogging?.includes('debug')) { this.platform.log.warn('[DEBUG]', String(...log)); diff --git a/src/homebridge-ui/public/index.html b/src/homebridge-ui/public/index.html new file mode 100644 index 00000000..022cc7de --- /dev/null +++ b/src/homebridge-ui/public/index.html @@ -0,0 +1,442 @@ +

+ homebridge-resideo logo +

+ + + + + + + \ No newline at end of file diff --git a/src/homebridge-ui/server.ts b/src/homebridge-ui/server.ts new file mode 100644 index 00000000..61c5e3b6 --- /dev/null +++ b/src/homebridge-ui/server.ts @@ -0,0 +1,161 @@ +/* eslint-disable no-console */ +import { HomebridgePluginUiServer } from '@homebridge/plugin-ui-utils'; +import { AuthorizeURL, TokenURL } from '../settings.js'; +import { request } from 'undici'; +import { createServer } from 'http'; +import fs from 'fs'; +import url from 'url'; + +class PluginUiServer extends HomebridgePluginUiServer { + public key!: string; + public secret!: string; + public hostname!: string; + constructor() { + super(); + this.onRequest('Start Resideo Login Server', (): any => { + const runningServer = createServer(async (req, res) => { + try { + res.writeHead(200, { 'Content-Type': 'text/html' }); + const urlParts = url.parse(req.url || '', true); + const pathArr = urlParts.pathname ? urlParts.pathname.split('?') : []; + const action = pathArr[0].replace('/', ''); + const query = urlParts.query; + switch (action) { + case 'start': { + this.key = query.key as string; + this.secret = query.secret as string; + this.hostname = query.host as string; + const { body, statusCode } = await request(AuthorizeURL, { + query: { + 'client_id': this.key, + 'redirect_uri': encodeURI('http://' + this.hostname + ':8585/auth'), + 'response_type': 'code', + }, + method: 'GET', + }); + console.log(`(Authroize) ${body}: ${JSON.stringify(body)}, statusCode: ${statusCode}`); + const url: any = await body.json(); + console.log(`(Authroize) json ${url}: ${JSON.stringify(url)}, statusCode: ${statusCode}`); + //const url = AuthorizeURL + '?response_type=code&redirect_uri=' + encodeURI('http://' + this.hostname + //+ ':8585/auth') + '&' + 'client_id=' + this.key; + res.end(''); + break; + } + case 'auth': { + if (query.code) { + const code = query.code; + const auth = Buffer.from(this.key + ':' + this.secret).toString('base64'); + const { body, statusCode } = await request(TokenURL, { + body: JSON.stringify({ + 'grant_type': 'authorization_code', + 'code': code, + 'redirect_uri': encodeURI('http://' + this.hostname + ':8585/auth'), + }), + headers: { + 'Authorization': `Basic ${auth}`, + 'Accept': 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded', + }, + method: 'POST', + }); + console.log(`(Token) ${body}: ${JSON.stringify(body)}, statusCode: ${statusCode}`); + const response: any = await body.json(); + console.log(`(Token) json ${response}: ${JSON.stringify(response)}, statusCode: ${statusCode}`); + /*const code = query.code; + const auth = Buffer.from(this.key + ':' + this.secret).toString('base64'); + let curlString = ''; + curlString += 'curl -X POST '; + curlString += '--header "Authorization: Basic ' + auth + '" '; + curlString += '--header "Accept: application/json" '; + curlString += '--header "Content-Type: application/x-www-form-urlencoded" '; + curlString += '-d "'; + curlString += 'grant_type=authorization_code&'; + curlString += 'code=' + code + '&'; + curlString += 'redirect_uri=' + encodeURI('http://' + this.hostname + ':8585/auth'); + curlString += '" '; + curlString += '"https://api.honeywell.com/oauth2/token"'; + try { + const { stdout } = await exec(curlString); + const response = JSON.parse(String(stdout));*/ + try { + if (response.access_token) { + this.pushEvent('creds-received', { + access: response.access_token, + key: this.key, + refresh: response.refresh_token, + secret: this.secret, + }); + res.end('Success. You can close this window now.'); + } else { + res.end('Failed to get access token. Close this window and start again'); + } + } catch (err) { + res.end('An error occurred:
' + JSON.stringify(err) + '

Close this window and start again'); + } + } else { + res.end('An error occurred:
no code received

Close this window and start again'); + } + break; + } default: { + // should never happen + res.end('welcome to the server'); + break; + } + }// end switch + } catch (err) { + console.log(err); + } + }); + runningServer.listen(8585, () => { + console.log('Server is running'); + }); + setTimeout(() => { + runningServer.close(); + }, 300000); + }); + + + + /* + A native method getCachedAccessories() was introduced in config-ui-x v4.37.0 + The following is for users who have a lower version of config-ui-x +*/ + + + this.onRequest('getCachedAccessories', () => { + try { + const plugin = 'homebridge-resideo'; + const devicesToReturn = []; + + // The path and file of the cached accessories + const accFile = this.homebridgeStoragePath + '/accessories/cachedAccessories'; + + // Check the file exists + if (fs.existsSync(accFile)) { + // read the cached accessories file + const cachedAccessories: any[] = JSON.parse(fs.readFileSync(accFile, 'utf8')); + + cachedAccessories.forEach((accessory: any) => { + // Check the accessory is from this plugin + if (accessory.plugin === plugin) { + // Add the cached accessory to the array + devicesToReturn.push(accessory.accessory as never); + } + }); + } + // Return the array + return devicesToReturn; + } catch (err) { + // Just return an empty accessory list in case of any errors + return []; + } + }); + this.ready(); + } +} + +function startPluginUiServer(): PluginUiServer { + return new PluginUiServer(); +} + +startPluginUiServer(); \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 62158223..639d95e4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,13 @@ +/* Copyright(C) 2017-2023, donavanbecker (https://github.com/donavanbecker). All rights reserved. + * + * index.ts: homebridge-resideo plugin registration. + */ +import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js'; import { API } from 'homebridge'; -import { ResideoPlatform } from './platform'; -import { PLATFORM_NAME } from './settings'; +import { ResideoPlatform } from './platform.js'; -/** - * This method registers the platform with Homebridge - */ -export = (api: API): void => { - api.registerPlatform(PLATFORM_NAME, ResideoPlatform); +// Register our platform with homebridge. +export default (api: API): void => { + + api.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, ResideoPlatform); }; diff --git a/src/platform.ts b/src/platform.ts index 5d068504..b00c0d14 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -1,14 +1,31 @@ -import axios, { AxiosInstance, InternalAxiosRequestConfig } from 'axios'; +/* Copyright(C) 2017-2023, donavanbecker (https://github.com/donavanbecker). All rights reserved. + * + * protect-platform.ts: homebridge-cloudflared-tunnel platform class. + */ +import { + DeviceURL, + LocationURL, + PLATFORM_NAME, + PLUGIN_NAME, + ResideoPlatformConfig, + T9groups, + TokenURL, + accessoryAttribute, + devicesConfig, + location, + resideoDevice, + sensorAccessory, +} from './settings.js'; +import { readFile } from 'fs/promises'; +import { request } from 'undici'; import { readFileSync, writeFileSync } from 'fs'; -import { API, Characteristic, DynamicPlatformPlugin, Logger, PlatformAccessory, Service } from 'homebridge'; +import { API, DynamicPlatformPlugin, Logging, PlatformAccessory } from 'homebridge'; import { stringify } from 'querystring'; -import superStringify from 'super-stringify'; -import { LeakSensor } from './devices/leaksensors'; -import { Valve } from './devices/valve'; -import { RoomSensors } from './devices/roomsensors'; -import { RoomSensorThermostat } from './devices/roomsensorthermostats'; -import { Thermostats } from './devices/thermostats'; -import * as settings from './settings'; +import { Valve } from './devices/valve.js'; +import { LeakSensor } from './devices/leaksensors.js'; +import { RoomSensors } from './devices/roomsensors.js'; +import { Thermostats } from './devices/thermostats.js'; +import { RoomSensorThermostat } from './devices/roomsensorthermostats.js'; /** * HomebridgePlatform @@ -16,38 +33,48 @@ import * as settings from './settings'; * parse the user config and discover/register accessories with Homebridge. */ export class ResideoPlatform implements DynamicPlatformPlugin { - public readonly Service: typeof Service = this.api.hap.Service; - public readonly Characteristic: typeof Characteristic = this.api.hap.Characteristic; - - // this is used to track restored cached accessories - public readonly accessories: PlatformAccessory[] = []; - public axios: AxiosInstance = axios.create({ - responseType: 'json', - }); + public accessories: PlatformAccessory[]; + public readonly api: API; + public readonly log: Logging; locations?: any; - firmware!: settings.accessoryAttribute['softwareRevision']; - sensorAccessory!: settings.sensorAccessory; - version = process.env.npm_package_version || '1.3.0'; + firmware!: accessoryAttribute['softwareRevision']; + sensorAccessory!: sensorAccessory; + version!: string; public sensorData = []; private refreshInterval!: NodeJS.Timeout; debugMode!: boolean; action!: string; - platformLogging!: string; - - constructor(public readonly log: Logger, public readonly config: settings.ResideoPlatformConfig, public readonly api: API) { - this.logs(); - this.debugLog(`Finished initializing platform: ${this.config.name}`); + config!: ResideoPlatformConfig; + platformConfig!: ResideoPlatformConfig['options']; + platformLogging!: ResideoPlatformConfig['logging']; + + constructor( + log: Logging, + config: ResideoPlatformConfig, + api: API, + ) { + this.accessories = []; + this.api = api; + this.log = log; // only load if configured - if (!this.config) { + if (!config) { return; } - // HOOBS notice - if (__dirname.includes('hoobs')) { - this.warnLog('This plugin has not been tested under HOOBS, it is highly recommended that ' + 'you switch to Homebridge: https://git.io/Jtxb0'); - } + // Plugin options into our config variables. + this.config = { + platform: 'ResideoPlatform', + credentials: config.credentials, + options: config.options, + }; + this.platformLogging = this.config.options?.logging ?? 'standard'; + this.platformConfigOptions(); + this.platformLogs(); + this.getVersion(); + this.debugLog(`Finished initializing platform: ${config.name}`); + // verify the config try { @@ -59,15 +86,6 @@ export class ResideoPlatform implements DynamicPlatformPlugin { return; } - // setup axios interceptor to add headers / api key to each request - this.axios.interceptors.request.use((request: InternalAxiosRequestConfig) => { - request.headers!.Authorization = `Bearer ${this.config.credentials?.accessToken}`; - request.params = request.params || {}; - request.params.apikey = this.config.credentials?.consumerKey; - request.headers!['Content-Type'] = 'application/json'; - return request; - }); - // When this event is fired it means Homebridge has restored all cached accessories from disk. // Dynamic Platform plugins should only register new accessories after this event was fired, // in order to ensure they weren't added to homebridge already. This event can also be used @@ -76,38 +94,46 @@ export class ResideoPlatform implements DynamicPlatformPlugin { this.debugLog('Executed didFinishLaunching callback'); // run the method to discover / register your devices as accessories await this.refreshAccessToken(); - try { - this.locations = await this.discoverlocations(); - } catch (e: any) { - this.action = 'Discober Locations'; - this.apiError(e); - } - try { - this.discoverDevices(); - } catch (e: any) { - this.action = 'Discober Device'; - this.apiError(e); + if (this.config.credentials?.accessToken) { + this.debugLog(`accessToken: ${this.config.credentials?.accessToken}`); + try { + this.locations = await this.discoverlocations(); + } catch (e: any) { + this.action = 'Discover Locations'; + this.apiError(e); + } + if (this.locations !== undefined) { + try { + this.discoverDevices(); + } catch (e: any) { + this.action = 'Discover Device'; + this.apiError(e); + } + } else { + this.log.error('Failed to Discover Locations. Re-Link Your Resideo Account.'); + } + } else { + this.log.error('Missing Access Token. Re-Link Your Resideo Account.'); } }); } - logs() { - this.debugMode = process.argv.includes('-D') || process.argv.includes('--debug'); - if (this.config.options?.logging === 'debug' || this.config.options?.logging === 'standard' || this.config.options?.logging === 'none') { - this.platformLogging = this.config.options!.logging; - if (this.platformLogging.includes('debug')) { - this.log.warn(`Using Config Logging: ${this.platformLogging}`); + async platformConfigOptions() { + const platformConfig: ResideoPlatformConfig['options'] = {}; + if (this.config.options) { + if (this.config.options.logging) { + platformConfig.logging = this.config.options.logging; } - } else if (this.debugMode) { - this.platformLogging = 'debugMode'; - if (this.platformLogging?.includes('debug')) { - this.log.warn(`Using ${this.platformLogging} Logging`); + if (this.config.options.refreshRate) { + platformConfig.refreshRate = this.config.options.refreshRate; } - } else { - this.platformLogging = 'standard'; - if (this.platformLogging?.includes('debug')) { - this.log.warn(`Using ${this.platformLogging} Logging`); + if (this.config.options.pushRate) { + platformConfig.pushRate = this.config.options.pushRate; + } + if (Object.entries(platformConfig).length !== 0) { + this.debugLog(`Platform Config: ${JSON.stringify(platformConfig)}`); } + this.platformConfig = platformConfig; } } @@ -127,25 +153,12 @@ export class ResideoPlatform implements DynamicPlatformPlugin { */ verifyConfig() { this.config.options = this.config.options || {}; - - const platformConfig = {}; - if (this.config.options.logging) { - platformConfig['logging'] = this.config.options.logging; - } - if (this.config.options.logging) { - platformConfig['refreshRate'] = this.config.options.refreshRate; - } - if (this.config.options.logging) { - platformConfig['pushRate'] = this.config.options.pushRate; - } - if (Object.entries(platformConfig).length !== 0) { - this.warnLog(`Platform Config: ${superStringify(platformConfig)}`); - } + this.config.credentials = this.config.credentials || {}; if (this.config.options) { // Device Config if (this.config.options.devices) { - for (const deviceConfig of this.config.options.devices!) { + for (const deviceConfig of this.config.options.devices) { if (!deviceConfig.hide_device && !deviceConfig.deviceClass) { throw new Error('The devices config section is missing the "Device Type" in the config, Check Your Conifg.'); } @@ -156,17 +169,17 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } } - if (this.config.options!.refreshRate! < 30) { + if (this.config.options.refreshRate! < 30) { throw new Error('Refresh Rate must be above 30 seconds.'); } if (this.config.disablePlugin) { - this.errorLog('Plugin is disabled.'); + this.log.error('Plugin is disabled.'); } if (!this.config.options.refreshRate && !this.config.disablePlugin) { // default 120 seconds (2 minutes) - this.config.options!.refreshRate! = 120; + this.config.options.refreshRate = 120; if (this.platformLogging?.includes('debug')) { this.warnLog('Using Default Refresh Rate of 2 Minutes.'); } @@ -174,7 +187,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { if (!this.config.options.pushRate && !this.config.disablePlugin) { // default 100 milliseconds - this.config.options!.pushRate! = 0.1; + this.config.options.pushRate = 0.1; if (this.platformLogging?.includes('debug')) { this.warnLog('Using Default Push Rate.'); } @@ -204,40 +217,36 @@ export class ResideoPlatform implements DynamicPlatformPlugin { */ async getAccessToken() { try { - let result: any; - - if (this.config.credentials!.consumerSecret) { - result = ( - await axios({ - url: settings.AuthURL, - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - auth: { - username: this.config.credentials!.consumerKey, - password: this.config.credentials!.consumerSecret, - }, - data: stringify({ - grant_type: 'refresh_token', - refresh_token: this.config.credentials!.refreshToken, - }), - responseType: 'json', - }) - ).data; + if (this.config.credentials!.consumerSecret && this.config.credentials!.consumerKey && this.config.credentials!.refreshToken) { + this.debugLog(`consumerKey: ${this.config.credentials!.consumerKey},` + ` consumerSecret: ${this.config.credentials!.consumerSecret},` + + ` refreshToken: ${this.config.credentials!.refreshToken}` + ` accessToken: ${this.config.credentials!.accessToken}`); + const { body, statusCode } = await request(TokenURL, { + method: 'POST', + headers: { + Authorization: + `Basic ${Buffer.from(`${this.config.credentials!.consumerKey}:${this.config.credentials!.consumerSecret}`).toString('base64')}`, + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: stringify({ + grant_type: 'refresh_token', + refresh_token: this.config.credentials!.refreshToken, + }), + }); + const action = 'getAccessToken'; + await this.statusCode(statusCode, action); + const result: any = await body.json(); + this.debugLog(`(getAccessToken) Result: ${JSON.stringify(result)}`); + this.config.credentials!.accessToken = result.access_token; + this.debugLog(`Got access token: ${this.config.credentials!.accessToken}`); + // check if the refresh token has changed + if (result.refresh_token !== this.config.credentials!.refreshToken) { + this.debugLog(`New refresh token: ${result.refresh_token}`); + await this.updateRefreshToken(result.refresh_token); + } + this.config.credentials!.refreshToken = result.refresh_token; } else { this.warnLog('Please re-link your account in the Homebridge UI.'); } - - this.config.credentials!.accessToken = result.access_token; - this.debugLog(`Got access token: ${this.config.credentials!.accessToken}`); - // check if the refresh token has changed - if (result.refresh_token !== this.config.credentials!.refreshToken) { - this.debugLog(`New refresh token: ${result.refresh_token}`); - await this.updateRefreshToken(result.refresh_token); - } - - this.config.credentials!.refreshToken = result.refresh_token; } catch (e: any) { this.action = 'refresh access token'; this.apiError(e); @@ -265,10 +274,10 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } // find this plugins current config - const pluginConfig = currentConfig.platforms.find((x: { platform: string }) => x.platform === settings.PLATFORM_NAME); + const pluginConfig = currentConfig.platforms.find((x: { platform: string }) => x.platform === PLATFORM_NAME); if (!pluginConfig) { - throw new Error(`Cannot find config for ${settings.PLATFORM_NAME} in platforms array`); + throw new Error(`Cannot find config for ${PLATFORM_NAME} in platforms array`); } // check the .credentials is an object before doing object things with it @@ -292,7 +301,22 @@ export class ResideoPlatform implements DynamicPlatformPlugin { * this method discovers the Locations */ async discoverlocations() { - const locations = (await this.axios.get(settings.LocationURL)).data; + this.debugLog(`accessToken: ${this.config.credentials?.accessToken}, consumerKey: ${this.config.credentials?.consumerKey}`); + const { body, statusCode } = await request(LocationURL, { + method: 'GET', + query: { + 'apikey': this.config.credentials?.consumerKey, + }, + headers: { + 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, + 'Content-Type': 'application/json', + }, + }); + const action = 'discoverlocations'; + await this.statusCode(statusCode, action); + const locations: any = await body.json(); + this.debugLog(`(discoverlocations) Location: ${JSON.stringify(locations)}`); + //const locations = (await this.axios.get(LocationURL)).data; return locations; } @@ -300,20 +324,37 @@ export class ResideoPlatform implements DynamicPlatformPlugin { * this method discovers the rooms at each location */ public async getCurrentSensorData( - device: settings.device & settings.devicesConfig, - group: settings.T9groups, - locationId: settings.location['locationID'], + device: resideoDevice & devicesConfig, + group: T9groups, + locationId: location['locationID'], ) { if (!this.sensorData[device.deviceID] || this.sensorData[device.deviceID].timestamp < Date.now()) { - const response: any = await this.axios.get(`${settings.DeviceURL}/thermostats/${device.deviceID}/group/${group.id}/rooms`, { + const { body, statusCode } = await request(`${DeviceURL}/thermostats/${device.deviceID}/group/${group.id}/rooms`, { + method: 'GET', + query: { + 'locationId': locationId, + 'apikey': this.config.credentials?.consumerKey, + }, + headers: { + 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, + 'Content-Type': 'application/json', + }, + }); + const action = 'getCurrentSensorData'; + await this.statusCode(statusCode, action); + const response: any = await body.json(); + this.debugLog(`(getCurrentSensorData) Response: ${JSON.stringify(response)}`); + /*const response: any = await this.axios.get(`${DeviceURL}/thermostats/${device.deviceID}/group/${group.id}/rooms`, { params: { locationId: locationId, }, - }); + });*/ + this.sensorData[device.deviceID] = { timestamp: Date.now() + 45000, - data: this.normalizeSensorDate(response.data), + data: this.normalizeSensorDate(response), }; + this.debugLog(`getCurrentSensorData ${device.deviceType} ${device.deviceModel}: ${this.sensorData[device.deviceID]}`); } else { this.debugLog(`getCurrentSensorData Cache ${device.deviceType} ${device.deviceModel} - ${device.userDefinedDeviceName}`); } @@ -335,7 +376,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { /** * this method discovers the firmware Veriosn for T9 Thermostats */ - public async getSoftwareRevision(locationId: settings.location['locationID'], device: settings.device & settings.devicesConfig) { + public async getSoftwareRevision(locationId: location['locationID'], device: resideoDevice & devicesConfig) { if (device.deviceModel.startsWith('T9') && device.groups) { for (const group of device.groups) { const roomsensors = await this.getCurrentSensorData(device, group, locationId); @@ -350,13 +391,14 @@ export class ResideoPlatform implements DynamicPlatformPlugin { if (accessories) { for (const key in accessories) { const sensorAccessory = accessories[key]; + this.debugLog(`sensorAccessory: ${JSON.stringify(sensorAccessory)}`); if ( sensorAccessory.accessoryAttribute && sensorAccessory.accessoryAttribute.type && sensorAccessory.accessoryAttribute.type.startsWith('Thermostat') ) { - this.debugLog(`Software Revision ${group.id} ${sensorAccessory.roomId} ${sensorAccessory.accessoryId} - ${sensorAccessory.accessoryAttribute.name} ${superStringify(sensorAccessory.accessoryAttribute.softwareRevision)}`); + this.debugLog(`groupId: ${group.id}, roomId: ${sensorAccessory.roomId}, accessoryId: ${sensorAccessory.accessoryId}, name: ` + + `${sensorAccessory.accessoryAttribute.name}, softwareRevision: ${sensorAccessory.accessoryAttribute.softwareRevision}`); return sensorAccessory.accessoryAttribute.softwareRevision; } else { this.debugLog(`No Thermostat ${device} ${group} ${locationId}`); @@ -391,18 +433,15 @@ export class ResideoPlatform implements DynamicPlatformPlugin { this.debugLog(`Total Devices Found at ${location.name}: ${location.devices.length}`); } const locationId = location.locationID; - this.locationinfo(location); - const deviceLists = location.devices; if (!this.config.options?.devices) { - this.debugLog(`No Resideo Device Config: ${superStringify(this.config.options?.devices)}`); + this.debugLog(`No Resideo Device Config: ${JSON.stringify(this.config.options?.devices)}`); const devices = deviceLists.map((v: any) => v); for (const device of devices) { - this.deviceinfo(device); await this.deviceClass(device, location, locationId); } } else { - this.debugLog(`Resideo Device Config Set: ${superStringify(this.config.options?.devices)}`); + this.debugLog(`Resideo Device Config Set: ${JSON.stringify(this.config.options?.devices)}`); const deviceConfigs = this.config.options?.devices; const mergeBydeviceID = (a1: { deviceID: string }[], a2: any[]) => @@ -412,19 +451,18 @@ export class ResideoPlatform implements DynamicPlatformPlugin { })); const devices = mergeBydeviceID(deviceLists, deviceConfigs); - this.debugLog(`Resideo Devices: ${superStringify(devices)}`); + this.debugLog(`Resideo Devices: ${JSON.stringify(devices)}`); for (const device of devices) { - this.deviceinfo(device); await this.deviceClass(device, location, locationId); } } } } else { - this.errorLog('Failed to Discover Locations. Re-Link Your Resideo Account.'); + this.log.error('Failed to Discover Locations. Re-Link Your Resideo Account.'); } } - private async deviceClass(device: settings.device & settings.devicesConfig, location: any, locationId: any) { + private async deviceClass(device: resideoDevice & devicesConfig, location: any, locationId: any) { switch (device.deviceClass) { case 'ShutoffValve': this.debugLog(`Discovered ${device.userDefinedDeviceName} ${device.deviceClass} @ ${location.name}`); @@ -437,8 +475,9 @@ export class ResideoPlatform implements DynamicPlatformPlugin { case 'Thermostat': this.debugLog(`Discovered ${device.userDefinedDeviceName} ${device.deviceClass} (${device.deviceModel}) @ ${location.name}`); await this.createThermostat(location, device, locationId); - if (device.deviceModel!.startsWith('T9')) { + if (device.deviceModel.startsWith('T9')) { try { + this.debugLog(`Discovering Room Sensor(s) for ${device.userDefinedDeviceName} ${device.deviceClass} (${device.deviceModel})`); await this.discoverRoomSensors(location.locationID, device); } catch (e: any) { this.action = 'Find Room Sensor(s)'; @@ -447,15 +486,16 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } break; default: - this.infoLog(`Device: ${device.userDefinedDeviceName} with Device Class: ${device.deviceClass} is currently not supported.`); - this.infoLog('Submit Feature Requests Here: https://git.io/JURLY'); + this.infoLog(`Device: ${device.userDefinedDeviceName} with Device Class: ` + + `${device.deviceClass} is currently not supported. Submit Feature Requests Here: https://git.io/JURLY`); } } - private async discoverRoomSensors(locationId: settings.location['locationID'], device: settings.device & settings.devicesConfig) { + private async discoverRoomSensors(locationId: location['locationID'], device: resideoDevice & devicesConfig) { // get the devices at each location this.roomsensordisplaymethod(device); if (device.groups) { + this.debugLog(`Discovered ${device.groups.length} Group(s) for ${device.userDefinedDeviceName} ${device.deviceClass} (${device.deviceModel})`); for (const group of device.groups) { const roomsensors = await this.getCurrentSensorData(device, group, locationId); for (const accessories of roomsensors) { @@ -467,7 +507,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { if (sensorAccessory.accessoryAttribute.type.startsWith('IndoorAirSensor')) { this.debugLog( `Discovered Room Sensor groupId: ${sensorAccessory.roomId},` + - ` roomId: ${sensorAccessory.accessoryId}, accessoryId: ${sensorAccessory.accessoryAttribute.name}`, + ` roomId: ${sensorAccessory.accessoryId}, accessoryId: ${sensorAccessory.accessoryAttribute.name}`, ); if (sensorAccessory.accessoryAttribute.model === '0') { sensorAccessory.accessoryAttribute.model = '4352'; @@ -485,7 +525,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } } - private roomsensordisplaymethod(device: settings.device & settings.devicesConfig) { + private roomsensordisplaymethod(device: resideoDevice & devicesConfig) { if (device.thermostat?.roompriority) { /** * Room Priority @@ -501,9 +541,9 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } private async createThermostat( - location: settings.location, - device: settings.device & settings.devicesConfig, - locationId: settings.location['locationID'], + location: location, + device: resideoDevice & devicesConfig, + locationId: location['locationID'], ) { const uuid = this.api.hap.uuid.generate(`${device.deviceID}-${device.deviceClass}`); @@ -554,13 +594,13 @@ export class ResideoPlatform implements DynamicPlatformPlugin { this.accessories.push(accessory); } else { if (this.platformLogging?.includes('debug')) { - this.errorLog(`Unable to Register new device: ${device.userDefinedDeviceName} ${device.deviceModel} ` + `DeviceID: ${device.deviceID}`); - this.errorLog('Check Config to see if DeviceID is being Hidden.'); + this.log.error(`Unable to Register new device: ${device.userDefinedDeviceName} ${device.deviceModel} ` + `DeviceID: ${device.deviceID}`); + this.log.error('Check Config to see if DeviceID is being Hidden.'); } } } - private Leak(device: settings.device & settings.devicesConfig, locationId: settings.location['locationID']) { + private Leak(device: resideoDevice & devicesConfig, locationId: location['locationID']) { const uuid = this.api.hap.uuid.generate(`${device.deviceID}-${device.deviceClass}`); // see if an accessory with the same uuid has already been registered and restored from @@ -613,13 +653,13 @@ export class ResideoPlatform implements DynamicPlatformPlugin { this.accessories.push(accessory); } else { if (this.platformLogging?.includes('debug')) { - this.errorLog(`Unable to Register new device: ${device.userDefinedDeviceName} ${device.deviceType} ` + ` DeviceID: ${device.deviceID}`); - this.errorLog('Check Config to see if DeviceID is being Hidden.'); + this.log.error(`Unable to Register new device: ${device.userDefinedDeviceName} ${device.deviceType} ` + ` DeviceID: ${device.deviceID}`); + this.log.error('Check Config to see if DeviceID is being Hidden.'); } } } - private Valve(device: settings.device & settings.devicesConfig, locationId: settings.location['locationID']) { + private Valve(device: resideoDevice & devicesConfig, locationId: location['locationID']) { const uuid = this.api.hap.uuid.generate(`${device.deviceID}-${device.deviceClass}`); // see if an accessory with the same uuid has already been registered and restored from @@ -672,17 +712,17 @@ export class ResideoPlatform implements DynamicPlatformPlugin { this.accessories.push(accessory); } else { if (this.platformLogging?.includes('debug')) { - this.errorLog(`Unable to Register new device: ${device.userDefinedDeviceName} ${device.deviceType} ` + ` DeviceID: ${device.deviceID}`); - this.errorLog('Check Config to see if DeviceID is being Hidden.'); + this.log.error(`Unable to Register new device: ${device.userDefinedDeviceName} ${device.deviceType} ` + ` DeviceID: ${device.deviceID}`); + this.log.error('Check Config to see if DeviceID is being Hidden.'); } } } private createRoomSensors( - device: settings.device & settings.devicesConfig, - locationId: settings.location['locationID'], - sensorAccessory: settings.sensorAccessory, - group: settings.T9groups, + device: resideoDevice & devicesConfig, + locationId: location['locationID'], + sensorAccessory: sensorAccessory, + group: T9groups, ) { // Room Sensor(s) const uuid = this.api.hap.uuid.generate(`${sensorAccessory.accessoryAttribute.type}-${sensorAccessory.accessoryId}-RoomSensor`); @@ -705,7 +745,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { new RoomSensors(this, existingAccessory, locationId, device, sensorAccessory, group); this.debugLog( `${sensorAccessory.accessoryAttribute.type}` + - ` uuid: ${sensorAccessory.accessoryAttribute.type}-${sensorAccessory.accessoryId}-RoomSensor, (${existingAccessory.UUID})`, + ` uuid: ${sensorAccessory.accessoryAttribute.type}-${sensorAccessory.accessoryId}-RoomSensor, (${existingAccessory.UUID})`, ); } else { this.unregisterPlatformAccessories(existingAccessory); @@ -731,7 +771,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { new RoomSensors(this, accessory, locationId, device, sensorAccessory, group); this.debugLog( `${sensorAccessory.accessoryAttribute.type}` + - ` uuid: ${sensorAccessory.accessoryAttribute.type}-${sensorAccessory.accessoryId}-RoomSensor, (${accessory.UUID})`, + ` uuid: ${sensorAccessory.accessoryAttribute.type}-${sensorAccessory.accessoryId}-RoomSensor, (${accessory.UUID})`, ); // publish device externally or link the accessory to your platform @@ -739,20 +779,20 @@ export class ResideoPlatform implements DynamicPlatformPlugin { this.accessories.push(accessory); } else { if (this.platformLogging?.includes('debug')) { - this.errorLog( + this.log.error( `Unable to Register new device: ${sensorAccessory.accessoryAttribute.name} ${sensorAccessory.accessoryAttribute.type} ` + - `DeviceID: ${sensorAccessory.deviceID}`, + `DeviceID: ${sensorAccessory.deviceID}`, ); - this.errorLog('Check Config to see if DeviceID is being Hidden.'); + this.log.error('Check Config to see if DeviceID is being Hidden.'); } } } private createRoomSensorThermostat( - device: settings.device & settings.devicesConfig, - locationId: settings.location['locationID'], - sensorAccessory: settings.sensorAccessory, - group: settings.T9groups, + device: resideoDevice & devicesConfig, + locationId: location['locationID'], + sensorAccessory: sensorAccessory, + group: T9groups, ) { const uuid = this.api.hap.uuid.generate(`${sensorAccessory.accessoryAttribute.type}-${sensorAccessory.accessoryId}-RoomSensorThermostat`); @@ -777,7 +817,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { new RoomSensorThermostat(this, existingAccessory, locationId, device, sensorAccessory, group); this.debugLog( `${sensorAccessory.accessoryAttribute.type} Thermostat uuid:` + - ` ${sensorAccessory.accessoryAttribute.type}-${sensorAccessory.accessoryId}-RoomSensorThermostat, (${existingAccessory.UUID})`, + ` ${sensorAccessory.accessoryAttribute.type}-${sensorAccessory.accessoryId}-RoomSensorThermostat, (${existingAccessory.UUID})`, ); } else { this.unregisterPlatformAccessories(existingAccessory); @@ -803,8 +843,8 @@ export class ResideoPlatform implements DynamicPlatformPlugin { new RoomSensorThermostat(this, accessory, locationId, device, sensorAccessory, group); this.debugLog( `${sensorAccessory.accessoryAttribute.type} Thermostat uuid:` + - ` ${sensorAccessory.accessoryAttribute.name}-${sensorAccessory.accessoryAttribute.type}-${sensorAccessory.accessoryId}-` + - `RoomSensorThermostat, (${accessory.UUID})`, + ` ${sensorAccessory.accessoryAttribute.name}-${sensorAccessory.accessoryAttribute.type}-${sensorAccessory.accessoryId}-` + + `RoomSensorThermostat, (${accessory.UUID})`, ); // publish device externally or link the accessory to your platform @@ -812,16 +852,16 @@ export class ResideoPlatform implements DynamicPlatformPlugin { this.accessories.push(accessory); } else { if (this.platformLogging?.includes('debug')) { - this.errorLog( + this.log.error( `Unable to Register new device: ${sensorAccessory.accessoryAttribute.name} ${sensorAccessory.accessoryAttribute.type} ` + - `DeviceID: ${sensorAccessory.deviceID}`, + `DeviceID: ${sensorAccessory.deviceID}`, ); - this.errorLog('Check Config to see if DeviceID is being Hidden.'); + this.log.error('Check Config to see if DeviceID is being Hidden.'); } } } - private leaksensorFirmwareNewAccessory(device: settings.device & settings.devicesConfig, accessory: PlatformAccessory) { + private leaksensorFirmwareNewAccessory(device: resideoDevice & devicesConfig, accessory: PlatformAccessory) { if (device.firmware) { accessory.context.firmwareRevision = device.firmware; } else { @@ -829,7 +869,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } } - private leaksensorFirmwareExistingAccessory(device: settings.device & settings.devicesConfig, existingAccessory: PlatformAccessory) { + private leaksensorFirmwareExistingAccessory(device: resideoDevice & devicesConfig, existingAccessory: PlatformAccessory) { if (device.firmware) { existingAccessory.context.firmwareRevision = device.firmware; } else { @@ -837,7 +877,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } } - private valveFirmwareNewAccessory(device: settings.device & settings.devicesConfig, accessory: PlatformAccessory) { + private valveFirmwareNewAccessory(device: resideoDevice & devicesConfig, accessory: PlatformAccessory) { if (device.firmware) { accessory.context.firmwareRevision = device.firmware; } else { @@ -845,7 +885,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } } - private valveFirmwareExistingAccessory(device: settings.device & settings.devicesConfig, existingAccessory: PlatformAccessory) { + private valveFirmwareExistingAccessory(device: resideoDevice & devicesConfig, existingAccessory: PlatformAccessory) { if (device.firmware) { existingAccessory.context.firmwareRevision = device.firmware; } else { @@ -853,7 +893,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } } - private roomsensorFirmwareNewAccessory(accessory, sensorAccessory: settings.sensorAccessory) { + private roomsensorFirmwareNewAccessory(accessory, sensorAccessory: sensorAccessory) { if (accessory.firmware) { accessory.context.firmwareRevision = accessory.firmware; } else { @@ -861,7 +901,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } } - private roomsensorFirmwareExistingAccessory(existingAccessory, sensorAccessory: settings.sensorAccessory) { + private roomsensorFirmwareExistingAccessory(existingAccessory, sensorAccessory: sensorAccessory) { if (existingAccessory.firmware) { existingAccessory.context.firmwareRevision = existingAccessory.firmware; } else { @@ -869,7 +909,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } } - public async thermostatFirmwareNewAccessory(device: settings.device & settings.devicesConfig, accessory: PlatformAccessory, location: any) { + public async thermostatFirmwareNewAccessory(device: resideoDevice & devicesConfig, accessory: PlatformAccessory, location: any) { if (device.firmware) { accessory.context.firmwareRevision = device.firmware; } else { @@ -889,7 +929,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } public async thermostatFirmwareExistingAccessory( - device: settings.device & settings.devicesConfig, + device: resideoDevice & devicesConfig, existingAccessory: PlatformAccessory, location: any, ) { @@ -911,152 +951,135 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } } - public async externalOrPlatform(device: settings.device & settings.devicesConfig, accessory: PlatformAccessory) { + public async externalOrPlatform(device: resideoDevice & devicesConfig, accessory: PlatformAccessory) { if (device.external) { - this.debugWarnLog(`${accessory.displayName} External Accessory Mode`); + this.warnLog(`${accessory.displayName} External Accessory Mode`); this.externalAccessory(accessory); } else { this.debugLog(`${accessory.displayName} External Accessory Mode: ${device.external}`); - this.api.registerPlatformAccessories(settings.PLUGIN_NAME, settings.PLATFORM_NAME, [accessory]); + this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); } } public async externalAccessory(accessory: PlatformAccessory) { - this.api.publishExternalAccessories(settings.PLUGIN_NAME, [accessory]); + this.api.publishExternalAccessories(PLUGIN_NAME, [accessory]); } public unregisterPlatformAccessories(existingAccessory: PlatformAccessory) { // remove platform accessories when no longer present - this.api.unregisterPlatformAccessories(settings.PLUGIN_NAME, settings.PLATFORM_NAME, [existingAccessory]); + this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory]); this.warnLog(`Removing existing accessory from cache: ${existingAccessory.displayName}`); } - public locationinfo(location: settings.location) { - if (this.platformLogging?.includes('debug')) { - if (location) { - this.warnLog(superStringify(location)); - } - } - } - - public deviceinfo(device: { - deviceID: string; - deviceType: string; - deviceClass: string; - deviceModel: string; - priorityType: string; - settings: settings.Settings; - inBuiltSensorState: settings.inBuiltSensorState; - groups: settings.device['groups'] & settings.devicesConfig; - }) { - if (this.platformLogging?.includes('debug')) { - this.warnLog(superStringify(device)); - if (device.deviceID) { - this.warnLog(superStringify(device.deviceID)); - this.errorLog(`Device ID: ${device.deviceID}`); - } - if (device.deviceType) { - this.warnLog(superStringify(device.deviceType)); - this.errorLog(`Device Type: ${device.deviceType}`); - } - if (device.deviceClass) { - this.warnLog(superStringify(device.deviceClass)); - this.errorLog(`Device Class: ${device.deviceClass}`); - } - if (device.deviceModel) { - this.warnLog(superStringify(device.deviceModel)); - this.errorLog(`Device Model: ${device.deviceModel}`); - } - if (device.priorityType) { - this.warnLog(superStringify(device.priorityType)); - this.errorLog(`Device Priority Type: ${device.priorityType}`); - } - if (device.settings) { - this.warnLog(superStringify(device.settings)); - if (device.settings.fan) { - this.warnLog(superStringify(device.settings.fan)); - this.errorLog(`Device Fan Settings: ${superStringify(device.settings.fan)}`); - if (device.settings.fan.allowedModes) { - this.warnLog(superStringify(device.settings.fan.allowedModes)); - this.errorLog(`Device Fan Allowed Modes: ${device.settings.fan.allowedModes}`); - } - if (device.settings.fan.changeableValues) { - this.warnLog(superStringify(device.settings.fan.changeableValues)); - this.errorLog(`Device Fan Changeable Values: ${superStringify(device.settings.fan.changeableValues)}`); - } - } - } - if (device.inBuiltSensorState) { - this.warnLog(superStringify(device.inBuiltSensorState)); - if (device.inBuiltSensorState.roomId) { - this.warnLog(superStringify(device.inBuiltSensorState.roomId)); - this.errorLog(`Device Built In Sensor Room ID: ${device.inBuiltSensorState.roomId}`); - } - if (device.inBuiltSensorState.roomName) { - this.warnLog(superStringify(device.inBuiltSensorState.roomName)); - this.errorLog(`Device Built In Sensor Room Name: ${device.inBuiltSensorState.roomName}`); - } - } - if (device.groups) { - this.warnLog(superStringify(device.groups)); - - for (const group of device.groups) { - this.errorLog(`Group: ${group.id}`); - } - } - } - } - apiError(e: any) { if (e.message.includes('400')) { - this.errorLog(`Failed to ${this.action}: Bad Request`); + this.log.error(`Failed to ${this.action}: Bad Request`); this.debugLog('The client has issued an invalid request. This is commonly used to specify validation errors in a request payload.'); } else if (e.message.includes('401')) { - this.errorLog(`Failed to ${this.action}: Unauthorized Request`); + this.log.error(`Failed to ${this.action}: Unauthorized Request`); this.debugLog('Authorization for the API is required, but the request has not been authenticated.'); } else if (e.message.includes('403')) { - this.errorLog(`Failed to ${this.action}: Forbidden Request`); + this.log.error(`Failed to ${this.action}: Forbidden Request`); this.debugLog('The request has been authenticated but does not have appropriate permissions, or a requested resource is not found.'); } else if (e.message.includes('404')) { - this.errorLog(`Failed to ${this.action}: Requst Not Found`); + this.log.error(`Failed to ${this.action}: Requst Not Found`); this.debugLog('Specifies the requested path does not exist.'); } else if (e.message.includes('406')) { - this.errorLog(`Failed to ${this.action}: Request Not Acceptable`); + this.log.error(`Failed to ${this.action}: Request Not Acceptable`); this.debugLog('The client has requested a MIME type via the Accept header for a value not supported by the server.'); } else if (e.message.includes('415')) { - this.errorLog(`Failed to ${this.action}: Unsupported Requst Header`); + this.log.error(`Failed to ${this.action}: Unsupported Requst Header`); this.debugLog('The client has defined a contentType header that is not supported by the server.'); } else if (e.message.includes('422')) { - this.errorLog(`Failed to ${this.action}: Unprocessable Entity`); + this.log.error(`Failed to ${this.action}: Unprocessable Entity`); this.debugLog( 'The client has made a valid request, but the server cannot process it.' + - ' This is often used for APIs for which certain limits have been exceeded.', + ' This is often used for APIs for which certain limits have been exceeded.', ); } else if (e.message.includes('429')) { - this.errorLog(`Failed to ${this.action}: Too Many Requests`); + this.log.error(`Failed to ${this.action}: Too Many Requests`); this.debugLog('The client has exceeded the number of requests allowed for a given time window.'); } else if (e.message.includes('500')) { - this.errorLog(`Failed to ${this.action}: Internal Server Error`); + this.log.error(`Failed to ${this.action}: Internal Server Error`); this.debugLog('An unexpected error on the SmartThings servers has occurred. These errors should be rare.'); } else { - this.errorLog(`Failed to ${this.action}`); + this.log.error(`Failed to ${this.action}`); } if (this.platformLogging?.includes('debug')) { - this.errorLog(`Failed to ${this.action}, Error Message: ${superStringify(e.message)}`); + this.log.error(`Failed to ${this.action}, Error Message: ${stringify(e.message)}`); + } + } + + async statusCode(statusCode: number, action: string): Promise { + switch (statusCode) { + case 200: + this.debugLog(`Standard Response, statusCode: ${statusCode}, Action: ${action}`); + break; + case 400: + this.log.error(`Bad Request, statusCode: ${statusCode}, Action: ${action}`); + break; + case 401: + this.log.error(`Unauthorized, statusCode: ${statusCode}, Action: ${action}`); + break; + case 404: + this.log.error(`Not Found, statusCode: ${statusCode}, Action: ${action}`); + break; + case 429: + this.log.error(`Too Many Requests, statusCode: ${statusCode}, Action: ${action}`); + break; + case 500: + this.log.error(`Internal Server Error (Meater Server), statusCode: ${statusCode}, Action: ${action}`); + break; + default: + this.infoLog(`Unknown statusCode: ${statusCode}, Report Bugs Here: https://bit.ly/homebridge-resideo-bug-report. Action: ${action}`); } } + async platformLogs() { + this.debugMode = process.argv.includes('-D') || process.argv.includes('--debug'); + if (this.config.options?.logging === 'debug' || this.config.options?.logging === 'standard' || this.config.options?.logging === 'none') { + this.platformLogging = this.config.options.logging; + if (this.platformLogging.includes('debug')) { + this.debugWarnLog(`Using Config Logging: ${this.platformLogging}`); + } + } else if (this.debugMode) { + this.platformLogging = 'debugMode'; + if (this.platformLogging?.includes('debug')) { + this.debugWarnLog(`Using ${this.platformLogging} Logging`); + } + } else { + this.platformLogging = 'standard'; + if (this.platformLogging?.includes('debug')) { + this.debugWarnLog(`Using ${this.platformLogging} Logging`); + } + } + if (this.debugMode) { + this.platformLogging = 'debugMode'; + } + } + + async getVersion() { + const json = JSON.parse( + await readFile( + new URL('../package.json', import.meta.url), + 'utf-8', + ), + ); + this.debugLog(`Plugin Version: ${json.version}`); + this.version = json.version; + } + /** * If device level logging is turned on, log to log.warn * Otherwise send debug logs to log.debug */ - infoLog(...log: any[]): void { + infoLog(...log: any[]) { if (this.enablingPlatfromLogging()) { this.log.info(String(...log)); } } - warnLog(...log: any[]): void { + warnLog(...log: any[]) { if (this.enablingPlatfromLogging()) { this.log.warn(String(...log)); } @@ -1065,12 +1088,12 @@ export class ResideoPlatform implements DynamicPlatformPlugin { debugWarnLog(...log: any[]): void { if (this.enablingPlatfromLogging()) { if (this.platformLogging?.includes('debug')) { - this.log.warn('[DEBUG]', String(...log)); + this.log.warn('[WARN]', String(...log)); } } } - errorLog(...log: any[]): void { + errorLog(...log: any[]) { if (this.enablingPlatfromLogging()) { this.log.error(String(...log)); } @@ -1079,15 +1102,15 @@ export class ResideoPlatform implements DynamicPlatformPlugin { debugErrorLog(...log: any[]): void { if (this.enablingPlatfromLogging()) { if (this.platformLogging?.includes('debug')) { - this.log.error('[DEBUG]', String(...log)); + this.log.error('[ERROR]', String(...log)); } } } - debugLog(...log: any[]): void { + debugLog(...log: any[]) { if (this.enablingPlatfromLogging()) { if (this.platformLogging === 'debugMode') { - this.log.debug(String(...log)); + this.log.debug('[HOMEBRIDGE DEBUGMODE]', String(...log)); } else if (this.platformLogging === 'debug') { this.log.info('[DEBUG]', String(...log)); } diff --git a/src/settings.ts b/src/settings.ts index 55a72f33..9e4da03c 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -12,35 +12,36 @@ export const PLUGIN_NAME = 'homebridge-resideo'; /** * This is the main url used to access Resideo API */ -export const AuthURL = 'https://api.honeywell.com/oauth2/token'; +export const AuthorizeURL = 'https://api.honeywell.com/oauth2/authorize'; /** * This is the main url used to access Resideo API */ -export const LocationURL = 'https://api.honeywell.com/v2/locations'; +export const TokenURL = 'https://api.honeywell.com/oauth2/token'; /** * This is the main url used to access Resideo API */ -export const DeviceURL = 'https://api.honeywell.com/v2/devices'; +export const LocationURL = 'https://api.honeywell.com/v2/locations'; /** - * This is the url used to access UI Login to Resideo API + * This is the main url used to access Resideo API */ -export const UIurl = 'https://homebridge-honeywell.iot.oz.nu/user/refresh'; +export const DeviceURL = 'https://api.honeywell.com/v2/devices'; //Config export interface ResideoPlatformConfig extends PlatformConfig { credentials?: credentials; disablePlugin?: boolean; options?: options | Record; + port?: string; } export type credentials = { - accessToken?: any; - consumerKey?: any; - consumerSecret?: any; - refreshToken?: any; + accessToken?: string; + consumerKey?: string; + consumerSecret?: string; + refreshToken?: string; }; export type options = { @@ -50,7 +51,7 @@ export type options = { logging?: string; }; -export interface devicesConfig extends device { +export interface devicesConfig extends resideoDevice { deviceClass: string; deviceID: string; thermostat?: thermostat; @@ -123,16 +124,17 @@ export type payload = { autoChangeoverActive?: boolean; thermostatSetpoint?: number; unit?: string; + state?: string; }; // Location export type location = { locationID: number; name: string; - devices: Array; + devices: Array; }; -export type device = { +export type resideoDevice = { groups?: Array; inBuiltSensorState?: inBuiltSensorState; settings?: Settings; @@ -183,6 +185,12 @@ export type device = { isRegistered: boolean; hasDeviceCheckedIn: boolean; isDeviceOffline: boolean; + deviceMac: string //Device MAC address + dataSyncInfo: dataSyncInfo; + lastCheckin: Date; //Last time data received from device + actuatorValve: actuatorValve; //Values specific to the valve operation + daylightSavingsInfo: daylightSavingsInfo //Daylight savings time config info + maintenance: maintenance //Maintenance settings }; export type T9groups = { @@ -406,7 +414,32 @@ export type Accessory = { detectMotion: boolean; }; -export interface AxiosRequestConfig { - params?: Record; - headers?: any; +export type dataSyncInfo = { + state: string; //'NotStarted' | 'Initiated' | 'Completed' | 'Failed' + transactionId: string; //Internal reference ID for the DataSync operation +} + +export type actuatorValve = { + commandSource: string;//'app' | 'wldFreeze' | 'wldLeak' | 'manual' | 'buildInLeak' | 'maintenance'; + runningTime: number; //Operation time + valveStatus: string //'unknown' | 'open' | 'close' | 'notOpen' | 'notClose' | 'opening' | 'closing' | 'antiScaleOpening' | 'antiScaleClosing'; + motorCycles: number; //Count of motor operations + motorCurrentAverage: number; + motorCurrentMax: number; + deviceTemperature: number; //Current temperature of device in Fahrenheit units + lastAntiScaleTime: Date; //Last time of anti - scale operation + leakStatus: string; //'ok' | 'leak' | 'na' | 'err' + timeValveChanged: Date; //Time of last valve change +} + +export type daylightSavingsInfo = { + isDaylightSaving: boolean //If device is currently using DST or not + nextOffsetChange: Date //Next scheduled DST changeover +} + +export type maintenance = { + antiScaleSettings: string; //Current anti - scale cycle: 'OncePerWeek' | 'OncePerTwoWeeks' | 'OncePerMonth' | 'OncePerTwoMonths' | 'Disabled' + antiScaleDOWSettings: string; //'Sunday' | 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday' + antiScaleDOMSettings: number; //If monthly anti - scale is used, day of the month. + antiScaleTimeSettings: string; //Time for anti - scale in 24 hrs format } diff --git a/tsconfig.json b/tsconfig.json index 41b030fc..b5ad9a38 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,27 @@ { "compilerOptions": { - "target": "ES2021", - "module": "commonjs", - "lib": ["ES2015", "ES2016", "ES2017", "ES2018", "ES2019", "ES2020", "ES2021"], + "target": "ES2022", + "module": "ES2022", + "lib": [ + "DOM", + "ES2022" + ], "declaration": true, "declarationMap": true, "sourceMap": true, - "outDir": "./dist", - "rootDir": "./src", + "outDir": "dist", + "rootDir": "src", "strict": true, "esModuleInterop": true, - "noImplicitAny": false + "noImplicitAny": false, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", }, - "include": ["src/"], - "exclude": ["**/*.spec.ts"] -} + "include": [ + "src" + ], + "exclude": [ + "**/*.spec.ts" + ] +} \ No newline at end of file